Skip to content

Commit 798437b

Browse files
committed
Make --use-winml sticky
Add 'build and test all' one-shot script and dev doc for dependencies required before running.
1 parent 98b9e4c commit 798437b

3 files changed

Lines changed: 372 additions & 0 deletions

File tree

sdk_v2/DEVELOPMENT.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# sdk_v2 — Developer Setup
2+
3+
A first-time contributor should be able to install the tools listed below,
4+
clone the repo, then run the one-shot build/test script from this directory
5+
and watch all four SDKs go green:
6+
7+
```powershell
8+
pwsh ./build_and_test_all.ps1
9+
```
10+
11+
If that passes, your machine is correctly configured.
12+
13+
## Prerequisites
14+
15+
All four SDKs (C++, C#, Python, JS/TS) build on **Windows**, **Linux**, and
16+
**macOS**. The WinML variant (`-UseWinml`) is Windows-only.
17+
18+
### All platforms
19+
20+
| Tool | Minimum version | Notes |
21+
| ---------------- | --------------- | ------------------------------------------------------------------------------------- |
22+
| Git | recent | LFS is **not** required. |
23+
| CMake | 3.20 | Driven by `sdk_v2/cpp/build.py`; do not invoke `cmake --build` directly. |
24+
| vcpkg | recent | Set `VCPKG_ROOT`, or use the copy bundled with Visual Studio (auto-detected). |
25+
| Python | 3.11–3.14, **64-bit** | Required by `build.py` and for the Python SDK. 32-bit Python will not work. |
26+
| .NET SDK | 8.0 | C# tests target `net8.0` (and `net462` on Windows, which comes from .NET Framework Targeting Pack via VS). |
27+
| Node.js | 20 LTS or newer | Brings `npm`. The JS SDK declares `"engines": { "node": ">=20" }`. |
28+
| PowerShell | 7+ (`pwsh`) | The one-shot script and `samples/js/test-v2.ps1` are written for PowerShell 7. |
29+
30+
### Windows-only
31+
32+
| Tool | Version | Notes |
33+
| --------------------------- | ------------------------ | --------------------------------------------------------------------- |
34+
| Visual Studio 2026 (v18) | Enterprise / Professional / Community | Install the **Desktop development with C++** and **.NET desktop development** workloads. Provides MSVC, Windows SDK, and vcpkg. |
35+
| Windows SDK | 10.0.26100 (VS workload) | Needed for the WinML target framework. |
36+
| .NET Framework 4.6.2 Targeting Pack | (VS workload) | One C# test target framework is `net462`. |
37+
38+
Launch your dev shell with **x64** explicitly — `Enter-VsDevShell` defaults
39+
to x86 and silently breaks the Python cffi extension build:
40+
41+
```powershell
42+
pwsh -NoExit -Command "& { Import-Module 'C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\Tools\Microsoft.VisualStudio.DevShell.dll'; Enter-VsDevShell <instance-id> -SkipAutomaticLocation -Arch amd64 -HostArch amd64 }"
43+
```
44+
45+
Verify with `echo $env:VSCMD_ARG_TGT_ARCH` — must be `x64`. (The one-shot
46+
script also forces `VSCMD_ARG_TGT_ARCH=x64` around the Python step as a
47+
safety net, but the C++ step does not, so getting the shell right matters.)
48+
49+
### Linux
50+
51+
* GCC 11+ or Clang 14+ (C++20 with `<format>` support).
52+
* `build-essential`, `ninja-build`, `pkg-config`, `curl`, `zip`, `unzip`, `tar`.
53+
* vcpkg dependencies: `autoconf`, `automake`, `libtool`, `python3-pip`.
54+
55+
### macOS
56+
57+
* Xcode 15+ command-line tools (Clang with C++20).
58+
* Homebrew packages: `cmake`, `ninja`, `pkg-config`, `python@3.11`, `node`, `dotnet-sdk`.
59+
60+
## What gets installed per-SDK
61+
62+
The one-shot script does not pre-install language toolchains, but it does
63+
install per-SDK package dependencies on first run:
64+
65+
| SDK | What runs |
66+
| ------ | -------------------------------------------------------------------------------------- |
67+
| C++ | `python build.py [--config ...] [--use_winml] [--skip_tests]` — configure + build + ctest. vcpkg restores native deps; ORT/GenAI come from NuGet via FetchContent (versions from `sdk_v2/deps_versions.json` or `_winml.json`). |
68+
| C# | `dotnet test Microsoft.AI.Foundry.Local.SDK.sln -c Release [-p:UseWinML=true]` — restores NuGet packages on demand. |
69+
| Python | `python -m pip install -e .[dev]` (compiles the cffi extension; needs MSVC/Clang) → `python -m pytest test/`. |
70+
| JS | `npm install` (runs `node-gyp` against the C++ build output) → `npm run build``npm test` (vitest). |
71+
72+
## Common knobs
73+
74+
```powershell
75+
# Full build + test, default config (RelWithDebInfo / Release).
76+
pwsh ./build_and_test_all.ps1
77+
78+
# WinML variant across all SDKs (Windows only).
79+
pwsh ./build_and_test_all.ps1 -UseWinml
80+
81+
# Just rebuild the native and the JS bindings, skip the slow C++ test pass.
82+
pwsh ./build_and_test_all.ps1 -Only cpp,js -SkipCppTests
83+
84+
# Skip one SDK.
85+
pwsh ./build_and_test_all.ps1 -Skip python
86+
87+
# Keep going past failures and print the summary at the end.
88+
pwsh ./build_and_test_all.ps1 -ContinueOnError
89+
```
90+
91+
See `pwsh ./build_and_test_all.ps1 -?` for the full parameter list.
92+
93+
## Troubleshooting
94+
95+
* **`cl.exe ... HostX86\x86\cl.exe` during pip install** — your shell has
96+
`VSCMD_ARG_TGT_ARCH=x86`. Re-launch with `-Arch amd64 -HostArch amd64`
97+
(see above). The one-shot script also guards against this for the Python
98+
step.
99+
* **Python build fails with `__stdcall`/`__cdecl` errors** — same root cause:
100+
cl.exe is targeting x86. Fix the shell.
101+
* **C# tests load the wrong native** — never invoke `cmake --build` directly
102+
or pass `--build_dir` to `build.py`. The C# tests pin an absolute path to
103+
`sdk_v2/cpp/build/<Platform>/<Config>/`; bypassing `build.py` puts the
104+
binary somewhere else.
105+
* **Switching between WinML and non-WinML** — the C++ FetchContent NuGet
106+
packages (`Microsoft.ML.OnnxRuntime.Foundry` vs `.WinML`) are cached and
107+
do not auto-refresh on variant flip. If you hit linker errors after
108+
toggling `-UseWinml`, wipe `sdk_v2/cpp/build/` and rerun.

sdk_v2/build_and_test_all.ps1

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
<#
2+
.SYNOPSIS
3+
Build and test all sdk_v2 SDKs (C++, C#, Python, JS) in one shot.
4+
5+
.DESCRIPTION
6+
The simple developer "build and run all tests" one-shot script for sdk_v2.
7+
8+
Order:
9+
1. C++ — python build.py (configure + build + test)
10+
2. C# — dotnet test (builds via project references)
11+
3. Python — pip install -e . then pytest
12+
4. JS — npm install + npm run build + npm test
13+
14+
Each SDK runs in its own step. The script stops on the first failure
15+
unless -ContinueOnError is supplied, and prints a per-SDK pass/fail
16+
summary at the end.
17+
18+
.PARAMETER UseWinml
19+
Build the WinML variant across all SDKs:
20+
* C++: passes --use_winml to build.py
21+
* C#: passes -p:UseWinML=true to dotnet test
22+
* Python: sets FL_PYTHON_PACKAGE_NAME=foundry-local-sdk-winml before pip install
23+
* JS: rebuilds the native addon against the WinML C++ build
24+
Windows only.
25+
26+
.PARAMETER Config
27+
C++ / C# build configuration. Default: RelWithDebInfo. Maps to dotnet
28+
Configuration=Release when set to RelWithDebInfo or Release.
29+
30+
.PARAMETER Skip
31+
SDKs to skip. Any of: cpp, cs, python, js.
32+
33+
.PARAMETER Only
34+
Run only the named SDKs. Overrides -Skip. Any of: cpp, cs, python, js.
35+
36+
.PARAMETER ContinueOnError
37+
Keep going after a failure instead of aborting on the first one.
38+
39+
.PARAMETER SkipCppTests
40+
Build C++ but skip the integration test run (still builds tests).
41+
Useful when you only want to rebuild the native library for the
42+
downstream SDKs.
43+
44+
.EXAMPLE
45+
pwsh ./build_and_test_all.ps1
46+
# Full build + test, no WinML.
47+
48+
.EXAMPLE
49+
pwsh ./build_and_test_all.ps1 -UseWinml
50+
# Full build + test against the WinML variant.
51+
52+
.EXAMPLE
53+
pwsh ./build_and_test_all.ps1 -Only cpp,js -SkipCppTests
54+
# Rebuild C++ (skip its tests) then build + test the JS SDK.
55+
#>
56+
[CmdletBinding()]
57+
param(
58+
[switch] $UseWinml,
59+
[ValidateSet('Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel')]
60+
[string] $Config = 'RelWithDebInfo',
61+
[ValidateSet('cpp', 'cs', 'python', 'js')]
62+
[string[]] $Skip = @(),
63+
[ValidateSet('cpp', 'cs', 'python', 'js')]
64+
[string[]] $Only,
65+
[switch] $ContinueOnError,
66+
[switch] $SkipCppTests
67+
)
68+
69+
$ErrorActionPreference = 'Stop'
70+
71+
$sdkRoot = $PSScriptRoot
72+
$cppDir = Join-Path $sdkRoot 'cpp'
73+
$csDir = Join-Path $sdkRoot 'cs'
74+
$pythonDir = Join-Path $sdkRoot 'python'
75+
$jsDir = Join-Path $sdkRoot 'js'
76+
77+
if ($UseWinml -and -not $IsWindows -and -not [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
78+
[System.Runtime.InteropServices.OSPlatform]::Windows)) {
79+
throw "-UseWinml is Windows-only."
80+
}
81+
82+
# Resolve which SDKs to run.
83+
$all = @('cpp', 'cs', 'python', 'js')
84+
if ($Only) {
85+
$targets = $all | Where-Object { $_ -in $Only }
86+
} else {
87+
$targets = $all | Where-Object { $_ -notin $Skip }
88+
}
89+
if (-not $targets) {
90+
Write-Host "Nothing to do." -ForegroundColor Yellow
91+
return
92+
}
93+
94+
# .NET Configuration maps RelWithDebInfo/MinSizeRel -> Release.
95+
$dotnetConfig = if ($Config -in @('Debug')) { 'Debug' } else { 'Release' }
96+
97+
$results = New-Object System.Collections.Generic.List[object]
98+
$overallStart = Get-Date
99+
100+
function Invoke-Step {
101+
param(
102+
[string] $Name,
103+
[scriptblock] $Action
104+
)
105+
Write-Host ""
106+
Write-Host "============================================================" -ForegroundColor Cyan
107+
Write-Host "==> [$Name] start (UseWinml=$UseWinml, Config=$Config)" -ForegroundColor Cyan
108+
Write-Host "============================================================" -ForegroundColor Cyan
109+
$start = Get-Date
110+
$ok = $false
111+
$note = ''
112+
try {
113+
& $Action
114+
$ok = $true
115+
} catch {
116+
$note = $_.Exception.Message
117+
Write-Host "[$Name] FAILED: $note" -ForegroundColor Red
118+
if (-not $ContinueOnError) {
119+
$script:results.Add([pscustomobject]@{
120+
Sdk = $Name
121+
Result = 'FAIL'
122+
Duration = ((Get-Date) - $start).ToString('mm\:ss')
123+
Note = $note
124+
})
125+
throw
126+
}
127+
}
128+
$script:results.Add([pscustomobject]@{
129+
Sdk = $Name
130+
Result = if ($ok) { 'OK' } else { 'FAIL' }
131+
Duration = ((Get-Date) - $start).ToString('mm\:ss')
132+
Note = $note
133+
})
134+
}
135+
136+
try {
137+
if ('cpp' -in $targets) {
138+
Invoke-Step 'cpp' {
139+
$args = @('build.py', '--config', $Config)
140+
if ($UseWinml) { $args += '--use_winml' }
141+
if ($SkipCppTests) { $args += '--skip_tests' }
142+
Push-Location $cppDir
143+
try {
144+
# build.py drives configure + build + test by default.
145+
python @args
146+
if ($LASTEXITCODE -ne 0) { throw "C++ build.py exit $LASTEXITCODE" }
147+
} finally {
148+
Pop-Location
149+
}
150+
}
151+
}
152+
153+
if ('cs' -in $targets) {
154+
Invoke-Step 'cs' {
155+
Push-Location $csDir
156+
try {
157+
$dotnetArgs = @(
158+
'test',
159+
'Microsoft.AI.Foundry.Local.SDK.sln',
160+
'-c', $dotnetConfig,
161+
'--nologo'
162+
)
163+
if ($UseWinml) { $dotnetArgs += '-p:UseWinML=true' }
164+
dotnet @dotnetArgs
165+
if ($LASTEXITCODE -ne 0) { throw "dotnet test exit $LASTEXITCODE" }
166+
} finally {
167+
Pop-Location
168+
}
169+
}
170+
}
171+
172+
if ('python' -in $targets) {
173+
Invoke-Step 'python' {
174+
Push-Location $pythonDir
175+
try {
176+
# Sanity check: the cffi extension links against an x64 foundry_local.dll,
177+
# so the Python interpreter MUST be 64-bit. A 32-bit Python here causes
178+
# cl.exe to compile for x86, which produces __stdcall/__cdecl mismatches
179+
# when verifying the function-pointer table in foundry_local_c.h.
180+
$pyInfo = python -c @"
181+
import struct, sys, sysconfig
182+
print(struct.calcsize('P') * 8)
183+
print(sysconfig.get_platform())
184+
print(sys.executable)
185+
"@
186+
if ($LASTEXITCODE -ne 0) { throw "python probe exit $LASTEXITCODE" }
187+
$bits, $plat, $exe = $pyInfo -split "`r?`n" | Where-Object { $_ }
188+
Write-Host "Using Python: $exe ($bits-bit, $plat)" -ForegroundColor DarkGray
189+
if ($bits -ne '64') {
190+
throw "Python at $exe is $bits-bit; sdk_v2/python requires a 64-bit interpreter."
191+
}
192+
193+
# On Windows, force setuptools' MSVC selection to target x64 regardless of
194+
# any inherited VSCMD/Platform state from a previous Developer Prompt.
195+
$restoreTgt = $env:VSCMD_ARG_TGT_ARCH
196+
$restoreHost = $env:VSCMD_ARG_HOST_ARCH
197+
$restorePlat = $env:Platform
198+
if ($IsWindows -or [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
199+
[System.Runtime.InteropServices.OSPlatform]::Windows)) {
200+
$env:VSCMD_ARG_TGT_ARCH = 'x64'
201+
$env:VSCMD_ARG_HOST_ARCH = 'x64'
202+
$env:Platform = 'x64'
203+
}
204+
205+
$env:FL_PYTHON_PACKAGE_NAME =
206+
if ($UseWinml) { 'foundry-local-sdk-winml' } else { 'foundry-local-sdk' }
207+
try {
208+
python -m pip install -e '.[dev]'
209+
if ($LASTEXITCODE -ne 0) { throw "pip install exit $LASTEXITCODE" }
210+
211+
python -m pytest test/ -v
212+
if ($LASTEXITCODE -ne 0) { throw "pytest exit $LASTEXITCODE" }
213+
} finally {
214+
Remove-Item Env:FL_PYTHON_PACKAGE_NAME -ErrorAction SilentlyContinue
215+
if ($null -eq $restoreTgt) { Remove-Item Env:VSCMD_ARG_TGT_ARCH -ErrorAction SilentlyContinue } else { $env:VSCMD_ARG_TGT_ARCH = $restoreTgt }
216+
if ($null -eq $restoreHost) { Remove-Item Env:VSCMD_ARG_HOST_ARCH -ErrorAction SilentlyContinue } else { $env:VSCMD_ARG_HOST_ARCH = $restoreHost }
217+
if ($null -eq $restorePlat) { Remove-Item Env:Platform -ErrorAction SilentlyContinue } else { $env:Platform = $restorePlat }
218+
}
219+
} finally {
220+
Pop-Location
221+
}
222+
}
223+
}
224+
225+
if ('js' -in $targets) {
226+
Invoke-Step 'js' {
227+
Push-Location $jsDir
228+
try {
229+
npm install
230+
if ($LASTEXITCODE -ne 0) { throw "npm install exit $LASTEXITCODE" }
231+
232+
# JS picks up the native library copied from the C++ build dir;
233+
# the WinML/non-WinML distinction is whichever C++ build ran above.
234+
npm run build
235+
if ($LASTEXITCODE -ne 0) { throw "npm run build exit $LASTEXITCODE" }
236+
237+
npm test
238+
if ($LASTEXITCODE -ne 0) { throw "npm test exit $LASTEXITCODE" }
239+
} finally {
240+
Pop-Location
241+
}
242+
}
243+
}
244+
} catch {
245+
# Already recorded by Invoke-Step. Fall through to summary.
246+
}
247+
248+
Write-Host ""
249+
Write-Host "============================================================" -ForegroundColor Cyan
250+
Write-Host "Summary (total: $(((Get-Date) - $overallStart).ToString('mm\:ss')))" -ForegroundColor Cyan
251+
Write-Host "============================================================" -ForegroundColor Cyan
252+
$results | Format-Table -AutoSize | Out-String | Write-Host
253+
254+
$failed = $results | Where-Object { $_.Result -ne 'OK' }
255+
if ($failed) {
256+
Write-Host "FAILED: $($failed.Sdk -join ', ')" -ForegroundColor Red
257+
exit 1
258+
} else {
259+
Write-Host "All SDKs passed." -ForegroundColor Green
260+
exit 0
261+
}

sdk_v2/cpp/build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,9 @@ def configure(args: argparse.Namespace) -> None:
466466
command += ["-DFOUNDRY_LOCAL_USE_WINML=ON"]
467467
if args.winml_sdk_version:
468468
command += [f"-DWINML_SDK_VERSION={args.winml_sdk_version}"]
469+
else:
470+
# Pass explicitly so a re-configure without the flag clears any cached ON value.
471+
command += ["-DFOUNDRY_LOCAL_USE_WINML=OFF"]
469472

470473
if args.ort_home:
471474
command += [f"-DORT_HOME={args.ort_home}"]

0 commit comments

Comments
 (0)