diff --git a/.github/workflows/build-rust-steps.yml b/.github/workflows/build-rust-steps.yml
index f007b7ee..810b6c1e 100644
--- a/.github/workflows/build-rust-steps.yml
+++ b/.github/workflows/build-rust-steps.yml
@@ -58,6 +58,13 @@ jobs:
Write-Host "Removed .cargo/config.toml crates-io redirect"
}
+ # Copy deps_versions.json into the crate directory so cargo package
+ # can include it and build.rs can find it during verify.
+ - name: Copy deps_versions.json for crate packaging
+ shell: pwsh
+ working-directory: ${{ github.workspace }}
+ run: Copy-Item sdk/deps_versions.json sdk/rust/deps_versions.json
+
- name: Checkout test-data-shared from Azure DevOps
if: ${{ inputs.run-integration-tests }}
shell: pwsh
diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml
index cb5766c0..d90a15e7 100644
--- a/.pipelines/foundry-local-packaging.yml
+++ b/.pipelines/foundry-local-packaging.yml
@@ -127,13 +127,13 @@ extends:
Write-Host "Python version: $pyVersion"
Write-Host "FLC version: $flcVersion"
- # ── Build & Test FLC ──
+ # ── Build FLC ──
- stage: build_core
- displayName: 'Build & Test FLC'
+ displayName: 'Build Core'
dependsOn: compute_version
jobs:
- job: flc_win_x64
- displayName: 'FLC win-x64'
+ displayName: 'Core win-x64'
pool:
name: onnxruntime-Win-CPU-2022
os: windows
@@ -153,7 +153,7 @@ extends:
platform: x64
- job: flc_win_arm64
- displayName: 'FLC win-arm64'
+ displayName: 'Core win-arm64'
pool:
name: onnxruntime-Win-CPU-2022
os: windows
@@ -171,7 +171,7 @@ extends:
platform: arm64
- job: flc_linux_x64
- displayName: 'FLC linux-x64'
+ displayName: 'Core linux-x64'
pool:
name: onnxruntime-Ubuntu2404-AMD-CPU
os: linux
@@ -189,7 +189,7 @@ extends:
platform: x64
- job: flc_osx_arm64
- displayName: 'FLC osx-arm64'
+ displayName: 'Core osx-arm64'
pool:
name: Azure Pipelines
vmImage: 'macOS-15'
@@ -207,13 +207,13 @@ extends:
flavor: osx-arm64
platform: arm64
- # ── Package FLC ──
- - stage: package_core
- displayName: 'Package FLC'
- dependsOn: build_core
- jobs:
- job: package_flc
- displayName: 'Package FLC'
+ displayName: 'Package Core'
+ dependsOn:
+ - flc_win_x64
+ - flc_win_arm64
+ - flc_linux_x64
+ - flc_osx_arm64
pool:
name: onnxruntime-Win-CPU-2022
os: windows
@@ -229,6 +229,9 @@ extends:
- output: pipelineArtifact
artifactName: 'flc-wheels'
targetPath: '$(Build.ArtifactStagingDirectory)/flc-wheels'
+ - output: pipelineArtifact
+ artifactName: 'deps-versions-standard'
+ targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions'
steps:
- checkout: neutron-server
clean: true
@@ -282,7 +285,8 @@ extends:
# ── Build C# SDK ──
- stage: build_cs
displayName: 'Build C# SDK'
- dependsOn: package_core
+ dependsOn:
+ - build_core
jobs:
- job: cs_sdk
displayName: 'Build'
@@ -297,6 +301,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-nuget'
targetPath: '$(Pipeline.Workspace)/flc-nuget'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-standard'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-standard'
outputs:
- output: pipelineArtifact
artifactName: 'cs-sdk'
@@ -313,11 +320,13 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: false
flcNugetDir: '$(Pipeline.Workspace)/flc-nuget'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard'
# ── Build JS SDK ──
- stage: build_js
displayName: 'Build JS SDK'
- dependsOn: package_core
+ dependsOn:
+ - build_core
jobs:
- job: js_sdk
displayName: 'Build'
@@ -332,6 +341,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-nuget'
targetPath: '$(Pipeline.Workspace)/flc-nuget'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-standard'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-standard'
outputs:
- output: pipelineArtifact
artifactName: 'js-sdk'
@@ -348,11 +360,13 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: false
flcNugetDir: '$(Pipeline.Workspace)/flc-nuget'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard'
# ── Build Python SDK ──
- stage: build_python
displayName: 'Build Python SDK'
- dependsOn: package_core
+ dependsOn:
+ - build_core
jobs:
- job: python_sdk
displayName: 'Build'
@@ -367,6 +381,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-wheels'
targetPath: '$(Pipeline.Workspace)/flc-wheels'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-standard'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-standard'
outputs:
- output: pipelineArtifact
artifactName: 'python-sdk'
@@ -383,11 +400,13 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: false
flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard'
# ── Build Rust SDK ──
- stage: build_rust
displayName: 'Build Rust SDK'
- dependsOn: package_core
+ dependsOn:
+ - build_core
jobs:
- job: rust_sdk
displayName: 'Build'
@@ -402,6 +421,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-nuget'
targetPath: '$(Pipeline.Workspace)/flc-nuget'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-standard'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-standard'
outputs:
- output: pipelineArtifact
artifactName: 'rust-sdk'
@@ -418,133 +440,15 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: false
flcNugetDir: '$(Pipeline.Workspace)/flc-nuget'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard'
- # ── Test C# SDK (win-x64) ──
- - stage: test_cs
- displayName: 'Test C# SDK'
- dependsOn: build_cs
- jobs:
- - job: test_cs_win_x64
- displayName: 'Test C# (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-nuget'
- targetPath: '$(Pipeline.Workspace)/flc-nuget'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-cs-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isWinML: false
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget'
-
- # TODO: Add macOS (osx-arm64) test job when a macOS ARM64 pool is available.
- # TODO: Add Linux (linux-x64) test job when Linux onnxruntime dependency is stabilized.
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Test JS SDK (win-x64) ──
- - stage: test_js
- displayName: 'Test JS SDK'
- dependsOn: build_js
- jobs:
- - job: test_js_win_x64
- displayName: 'Test JS (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-nuget'
- targetPath: '$(Pipeline.Workspace)/flc-nuget'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-js-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isWinML: false
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget'
-
- # TODO: Add macOS (osx-arm64) test job when a macOS ARM64 pool is available.
- # TODO: Add Linux (linux-x64) test job when Linux onnxruntime dependency is stabilized.
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Test Python SDK (win-x64) ──
- - stage: test_python
- displayName: 'Test Python SDK'
- dependsOn: build_python
- jobs:
- - job: test_python_win_x64
- displayName: 'Test Python (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-wheels'
- targetPath: '$(Pipeline.Workspace)/flc-wheels'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-python-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isWinML: false
- flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels'
-
- # TODO: Add macOS (osx-arm64) test job when a macOS ARM64 pool is available.
- # TODO: Add Linux (linux-x64) test job when Linux onnxruntime dependency is stabilized.
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Test Rust SDK (win-x64) ──
- - stage: test_rust
- displayName: 'Test Rust SDK'
- dependsOn: build_rust
- jobs:
- - job: test_rust_win_x64
- displayName: 'Test Rust (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-nuget'
- targetPath: '$(Pipeline.Workspace)/flc-nuget'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-rust-steps.yml@self
- parameters:
- isWinML: false
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget'
-
- # TODO: Add macOS (osx-arm64) test job when a macOS ARM64 pool is available.
- # TODO: Add Linux (linux-x64) test job when Linux onnxruntime dependency is stabilized.
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Build & Test FLC (WinML) ──
+ # ── Build FLC (WinML) ──
- stage: build_core_winml
- displayName: 'Build & Test FLC WinML'
+ displayName: 'Build Core (WinML)'
dependsOn: compute_version
jobs:
- job: flc_winml_win_x64
- displayName: 'FLC win-x64 (WinML)'
+ displayName: 'Core win-x64 (WinML)'
pool:
name: onnxruntime-Win-CPU-2022
os: windows
@@ -565,7 +469,7 @@ extends:
isWinML: true
- job: flc_winml_win_arm64
- displayName: 'FLC win-arm64 (WinML)'
+ displayName: 'Core win-arm64 (WinML)'
pool:
name: onnxruntime-Win-CPU-2022
os: windows
@@ -583,13 +487,11 @@ extends:
platform: arm64
isWinML: true
- # ── Package FLC (WinML) ──
- - stage: package_core_winml
- displayName: 'Package FLC WinML'
- dependsOn: build_core_winml
- jobs:
- job: package_flc_winml
- displayName: 'Package FLC (WinML)'
+ displayName: 'Package Core (WinML)'
+ dependsOn:
+ - flc_winml_win_x64
+ - flc_winml_win_arm64
pool:
name: onnxruntime-Win-CPU-2022
os: windows
@@ -605,6 +507,9 @@ extends:
- output: pipelineArtifact
artifactName: 'flc-wheels-winml'
targetPath: '$(Build.ArtifactStagingDirectory)/flc-wheels'
+ - output: pipelineArtifact
+ artifactName: 'deps-versions-winml'
+ targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions'
steps:
- checkout: neutron-server
clean: true
@@ -643,8 +548,9 @@ extends:
# ── Build C# SDK (WinML) ──
- stage: build_cs_winml
- displayName: 'Build C# SDK WinML'
- dependsOn: package_core_winml
+ displayName: 'Build C# SDK (WinML)'
+ dependsOn:
+ - build_core_winml
jobs:
- job: cs_sdk_winml
displayName: 'Build'
@@ -659,6 +565,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-nuget-winml'
targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-winml'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-winml'
outputs:
- output: pipelineArtifact
artifactName: 'cs-sdk-winml'
@@ -675,12 +584,14 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: true
flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml'
outputDir: '$(Build.ArtifactStagingDirectory)/cs-sdk-winml'
# ── Build JS SDK (WinML) ──
- stage: build_js_winml
- displayName: 'Build JS SDK WinML'
- dependsOn: package_core_winml
+ displayName: 'Build JS SDK (WinML)'
+ dependsOn:
+ - build_core_winml
jobs:
- job: js_sdk_winml
displayName: 'Build'
@@ -695,6 +606,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-nuget-winml'
targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-winml'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-winml'
outputs:
- output: pipelineArtifact
artifactName: 'js-sdk-winml'
@@ -711,11 +625,13 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: true
flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml'
# ── Build Python SDK (WinML) ──
- stage: build_python_winml
- displayName: 'Build Python SDK WinML'
- dependsOn: package_core_winml
+ displayName: 'Build Python SDK (WinML)'
+ dependsOn:
+ - build_core_winml
jobs:
- job: python_sdk_winml
displayName: 'Build'
@@ -730,6 +646,9 @@ extends:
- input: pipelineArtifact
artifactName: 'flc-wheels-winml'
targetPath: '$(Pipeline.Workspace)/flc-wheels-winml'
+ - input: pipelineArtifact
+ artifactName: 'deps-versions-winml'
+ targetPath: '$(Pipeline.Workspace)/deps-versions-winml'
outputs:
- output: pipelineArtifact
artifactName: 'python-sdk-winml'
@@ -746,152 +665,49 @@ extends:
prereleaseId: ${{ parameters.prereleaseId }}
isWinML: true
flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml'
+ depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml'
outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk-winml'
- # ── Build Rust SDK (WinML) ──
- - stage: build_rust_winml
- displayName: 'Build Rust SDK WinML'
- dependsOn: package_core_winml
- jobs:
- - job: rust_sdk_winml
- displayName: 'Build'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'version-info'
- targetPath: '$(Pipeline.Workspace)/version-info'
- - input: pipelineArtifact
- artifactName: 'flc-nuget-winml'
- targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
- outputs:
- - output: pipelineArtifact
- artifactName: 'rust-sdk-winml'
- targetPath: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/build-rust-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isRelease: ${{ parameters.isRelease }}
- prereleaseId: ${{ parameters.prereleaseId }}
- isWinML: true
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
- outputDir: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml'
-
- # ── Test C# SDK WinML (win-x64) ──
- - stage: test_cs_winml
- displayName: 'Test C# SDK WinML'
- dependsOn: build_cs_winml
- jobs:
- - job: test_cs_winml_win_x64
- displayName: 'Test C# WinML (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-nuget-winml'
- targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-cs-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isWinML: true
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
-
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Test JS SDK WinML (win-x64) ──
- - stage: test_js_winml
- displayName: 'Test JS SDK WinML'
- dependsOn: build_js_winml
- jobs:
- - job: test_js_winml_win_x64
- displayName: 'Test JS WinML (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-nuget-winml'
- targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-js-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isWinML: true
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
-
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Test Python SDK WinML (win-x64) ──
- - stage: test_python_winml
- displayName: 'Test Python SDK WinML'
- dependsOn: build_python_winml
- jobs:
- - job: test_python_winml_win_x64
- displayName: 'Test Python WinML (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-wheels-winml'
- targetPath: '$(Pipeline.Workspace)/flc-wheels-winml'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-python-steps.yml@self
- parameters:
- version: ${{ parameters.version }}
- isWinML: true
- flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml'
-
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
- # ── Test Rust SDK WinML (win-x64) ──
- - stage: test_rust_winml
- displayName: 'Test Rust SDK WinML'
- dependsOn: build_rust_winml
- jobs:
- - job: test_rust_winml_win_x64
- displayName: 'Test Rust WinML (win-x64)'
- pool:
- name: onnxruntime-Win-CPU-2022
- os: windows
- templateContext:
- inputs:
- - input: pipelineArtifact
- artifactName: 'flc-nuget-winml'
- targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
- steps:
- - checkout: self
- clean: true
- - checkout: test-data-shared
- lfs: true
- - template: .pipelines/templates/test-rust-steps.yml@self
- parameters:
- isWinML: true
- flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
-
- # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available.
-
+ # Rust SDK has one package with different install options for standard vs WinML,
+ # so we only publish once under the standard stage and skip the WinML stage. Leaving
+ # it as a commented block incase we decide to publish separate Rust WinML package in the future.
+ # # ── Build Rust SDK (WinML) ──
+ # - stage: build_rust_winml
+ # displayName: 'Build Rust SDK (WinML)'
+ # dependsOn:
+ # - build_core_winml
+ # jobs:
+ # - job: rust_sdk_winml
+ # displayName: 'Build'
+ # pool:
+ # name: onnxruntime-Win-CPU-2022
+ # os: windows
+ # templateContext:
+ # inputs:
+ # - input: pipelineArtifact
+ # artifactName: 'version-info'
+ # targetPath: '$(Pipeline.Workspace)/version-info'
+ # - input: pipelineArtifact
+ # artifactName: 'flc-nuget-winml'
+ # targetPath: '$(Pipeline.Workspace)/flc-nuget-winml'
+ # - input: pipelineArtifact
+ # artifactName: 'deps-versions-winml'
+ # targetPath: '$(Pipeline.Workspace)/deps-versions-winml'
+ # outputs:
+ # - output: pipelineArtifact
+ # artifactName: 'rust-sdk-winml'
+ # targetPath: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml'
+ # steps:
+ # - checkout: self
+ # clean: true
+ # - checkout: test-data-shared
+ # lfs: true
+ # - template: .pipelines/templates/build-rust-steps.yml@self
+ # parameters:
+ # version: ${{ parameters.version }}
+ # isRelease: ${{ parameters.isRelease }}
+ # prereleaseId: ${{ parameters.prereleaseId }}
+ # isWinML: true
+ # flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml'
+ # depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml'
+ # outputDir: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml'
\ No newline at end of file
diff --git a/.pipelines/templates/build-cs-steps.yml b/.pipelines/templates/build-cs-steps.yml
index 38f5b8bf..5d8f67c1 100644
--- a/.pipelines/templates/build-cs-steps.yml
+++ b/.pipelines/templates/build-cs-steps.yml
@@ -20,6 +20,10 @@ parameters:
- name: prereleaseId
type: string
default: ''
+- name: depsVersionsDir
+ type: string
+ default: ''
+ displayName: 'Path to deps-versions artifact directory'
steps:
# Set paths for multi-repo checkout
- task: PowerShell@2
@@ -48,6 +52,13 @@ steps:
Write-Host "Package version: $v"
Write-Host "##vso[task.setvariable variable=packageVersion]$v"
+# Load dependency versions from deps_versions.json
+- template: update-deps-versions-steps.yml
+ parameters:
+ repoRoot: $(repoRoot)
+ artifactDir: ${{ parameters.depsVersionsDir }}
+ isWinML: ${{ parameters.isWinML }}
+
# List downloaded artifact for debugging
- task: PowerShell@2
displayName: 'List downloaded FLC artifact'
@@ -74,14 +85,9 @@ steps:
"@
- # Determine the FLC version from the .nupkg filename
+ # Point the local NuGet feed at the directory that actually contains the .nupkg
$nupkg = Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse -Filter "Microsoft.AI.Foundry.Local.Core*.nupkg" -Exclude "*.snupkg" | Select-Object -First 1
if (-not $nupkg) { throw "No FLC .nupkg found in ${{ parameters.flcNugetDir }}" }
- $flcVer = $nupkg.BaseName -replace '^Microsoft\.AI\.Foundry\.Local\.Core(\.WinML)?\.', ''
- Write-Host "##vso[task.setvariable variable=resolvedFlcVersion]$flcVer"
- Write-Host "Resolved FLC version: $flcVer"
-
- # Point the local NuGet feed at the directory that actually contains the .nupkg
$flcFeedDir = $nupkg.DirectoryName
$nugetConfig = $nugetConfig -replace [regex]::Escape("${{ parameters.flcNugetDir }}"), $flcFeedDir
$configPath = "$(Build.ArtifactStagingDirectory)/NuGet.config"
@@ -182,3 +188,44 @@ steps:
signConfigType: inlineSignParams
inlineOperation: |
[{"keyCode":"CP-401405","operationSetCode":"NuGetSign","parameters":[],"toolName":"sign","toolVersion":"6.2.9304.0"},{"keyCode":"CP-401405","operationSetCode":"NuGetVerify","parameters":[],"toolName":"sign","toolVersion":"6.2.9304.0"}]
+
+# ── Tests ──
+- ${{ if eq(parameters.isWinML, true) }}:
+ - task: PowerShell@2
+ displayName: 'Install Windows App SDK Runtime'
+ inputs:
+ targetType: 'inline'
+ script: |
+ $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe"
+ $installerPath = "$env:TEMP\windowsappruntimeinstall.exe"
+ Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
+ & $installerPath --quiet --force
+ if ($LASTEXITCODE -ne 0) { throw "Windows App SDK Runtime install failed" }
+ errorActionPreference: 'stop'
+
+- task: PowerShell@2
+ displayName: 'Restore & build tests'
+ inputs:
+ targetType: inline
+ script: |
+ dotnet restore "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" `
+ --configfile "$(customNugetConfig)" `
+ /p:UseWinML=${{ parameters.isWinML }}
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+ dotnet build "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" `
+ --no-restore --configuration Release `
+ /p:UseWinML=${{ parameters.isWinML }}
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+- task: PowerShell@2
+ displayName: 'Run SDK tests'
+ inputs:
+ targetType: inline
+ script: |
+ dotnet test "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" `
+ --no-build --configuration Release `
+ /p:UseWinML=${{ parameters.isWinML }}
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ env:
+ TF_BUILD: 'true'
diff --git a/.pipelines/templates/build-js-steps.yml b/.pipelines/templates/build-js-steps.yml
index 3aa2908d..ca42fea1 100644
--- a/.pipelines/templates/build-js-steps.yml
+++ b/.pipelines/templates/build-js-steps.yml
@@ -17,6 +17,10 @@ parameters:
- name: prereleaseId
type: string
default: ''
+- name: depsVersionsDir
+ type: string
+ default: ''
+ displayName: 'Path to deps-versions artifact directory'
steps:
# Set paths for multi-repo checkout
- task: PowerShell@2
@@ -45,7 +49,14 @@ steps:
inputs:
versionSpec: '20.x'
-# Read version from the version-info artifact produced by compute_version stage.
+# Load dependency versions from deps_versions.json
+- template: update-deps-versions-steps.yml
+ parameters:
+ repoRoot: $(repoRoot)
+ artifactDir: ${{ parameters.depsVersionsDir }}
+ isWinML: ${{ parameters.isWinML }}
+
+# Compute version
- task: PowerShell@2
displayName: 'Set package version'
inputs:
@@ -55,13 +66,66 @@ steps:
Write-Host "Package version: $v"
Write-Host "##vso[task.setvariable variable=packageVersion]$v"
-# Install dependencies including native binaries (FLC, ORT, GenAI) from NuGet feeds
+# Install JS dependencies. When a pipeline-built FLC artifact is provided,
+# use --ignore-scripts to skip the native binary download (which would 404
+# on the unpublished FLC package), then extract FLC from the local artifact
+# and run the install script manually to fetch ORT/GenAI from public feeds.
- task: Npm@1
- displayName: 'npm install'
+ displayName: 'npm install (skip native downloads)'
inputs:
command: custom
workingDir: $(repoRoot)/sdk/js
- customCommand: 'install'
+ customCommand: 'install --ignore-scripts'
+
+- task: PowerShell@2
+ displayName: 'Extract FLC from pipeline-built artifact'
+ inputs:
+ targetType: inline
+ script: |
+ $os = 'win32'
+ $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
+ $platformKey = "$os-$arch"
+ $rid = if ($arch -eq 'arm64') { 'win-arm64' } else { 'win-x64' }
+
+ if ($IsLinux) {
+ $os = 'linux'
+ $platformKey = "$os-$arch"
+ $rid = "linux-$arch"
+ } elseif ($IsMacOS) {
+ $os = 'darwin'
+ $platformKey = "$os-$arch"
+ $rid = "osx-$arch"
+ }
+
+ $nupkg = Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse -Filter "Microsoft.AI.Foundry.Local.Core*.nupkg" -Exclude "*.snupkg" | Select-Object -First 1
+ if (-not $nupkg) { throw "No FLC .nupkg found in ${{ parameters.flcNugetDir }}" }
+
+ $extractDir = "$(Build.ArtifactStagingDirectory)/flc-extract"
+ $zip = [System.IO.Path]::ChangeExtension($nupkg.FullName, ".zip")
+ Copy-Item $nupkg.FullName $zip -Force
+ Expand-Archive -Path $zip -DestinationPath $extractDir -Force
+
+ # Place FLC binary so the install script skips downloading it
+ $destDir = "$(repoRoot)/sdk/js/node_modules/@foundry-local-core/$platformKey"
+ New-Item -ItemType Directory -Path $destDir -Force | Out-Null
+ $nativeDir = "$extractDir/runtimes/$rid/native"
+ if (Test-Path $nativeDir) {
+ Get-ChildItem $nativeDir -File | ForEach-Object {
+ Copy-Item $_.FullName -Destination "$destDir/$($_.Name)" -Force
+ Write-Host "Placed $($_.Name) from pipeline artifact"
+ }
+ } else {
+ Write-Warning "No native binaries found at $nativeDir for RID $rid"
+ }
+
+- task: PowerShell@2
+ displayName: 'Run native binary install (ORT + GenAI)'
+ inputs:
+ targetType: inline
+ script: |
+ Set-Location "$(repoRoot)/sdk/js"
+ node script/preinstall.cjs
+ node script/install-standard.cjs
# Overwrite the FLC native binary with the one we just built
- task: PowerShell@2
@@ -149,3 +213,26 @@ steps:
$destDir = "$(Build.ArtifactStagingDirectory)/js-sdk"
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
Copy-Item "$(repoRoot)/sdk/js/*.tgz" "$destDir/"
+
+# ── Tests ──
+- ${{ if eq(parameters.isWinML, true) }}:
+ - task: PowerShell@2
+ displayName: 'Install Windows App SDK Runtime'
+ inputs:
+ targetType: 'inline'
+ script: |
+ $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe"
+ $installerPath = "$env:TEMP\windowsappruntimeinstall.exe"
+ Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
+ & $installerPath --quiet --force
+ if ($LASTEXITCODE -ne 0) { throw "Windows App SDK Runtime install failed" }
+ errorActionPreference: 'stop'
+
+- task: Npm@1
+ displayName: 'npm test'
+ inputs:
+ command: custom
+ workingDir: $(repoRoot)/sdk/js
+ customCommand: 'test'
+ env:
+ TF_BUILD: 'true'
diff --git a/.pipelines/templates/build-python-steps.yml b/.pipelines/templates/build-python-steps.yml
index a8658772..5ada9cb6 100644
--- a/.pipelines/templates/build-python-steps.yml
+++ b/.pipelines/templates/build-python-steps.yml
@@ -20,6 +20,10 @@ parameters:
- name: prereleaseId
type: string
default: ''
+- name: depsVersionsDir
+ type: string
+ default: ''
+ displayName: 'Path to deps-versions artifact directory'
steps:
# Set paths for multi-repo checkout
- task: PowerShell@2
@@ -37,6 +41,13 @@ steps:
inputs:
versionSpec: '3.12'
+# Load dependency versions from deps_versions.json
+- template: update-deps-versions-steps.yml
+ parameters:
+ repoRoot: $(repoRoot)
+ artifactDir: ${{ parameters.depsVersionsDir }}
+ isWinML: ${{ parameters.isWinML }}
+
# List downloaded FLC wheels for debugging
- task: PowerShell@2
displayName: 'List downloaded FLC wheels'
@@ -103,12 +114,19 @@ steps:
Write-Warning "No FLC wheel found matching $filter in ${{ parameters.flcWheelsDir }}"
}
-- ${{ if eq(parameters.isWinML, true) }}:
- - script: pip install onnxruntime-core==1.23.2.3 onnxruntime-genai-core==0.13.1
- displayName: 'Install ORT native packages (WinML)'
-- ${{ else }}:
- - script: pip install onnxruntime-core==1.24.4 onnxruntime-genai-core==0.13.1
- displayName: 'Install ORT native packages'
+- task: PowerShell@2
+ displayName: 'Install ORT native packages'
+ inputs:
+ targetType: inline
+ script: |
+ $isWinML = "${{ parameters.isWinML }}" -eq "True"
+ $fileName = if ($isWinML) { "deps_versions_winml.json" } else { "deps_versions.json" }
+ $deps = Get-Content "$(repoRoot)/sdk/$fileName" -Raw | ConvertFrom-Json
+ $ortVer = $deps.onnxruntime.version
+ $genaiVer = $deps.'onnxruntime-genai'.version
+ Write-Host "Installing onnxruntime-core==$ortVer onnxruntime-genai-core==$genaiVer"
+ pip install "onnxruntime-core==$ortVer" "onnxruntime-genai-core==$genaiVer"
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- script: pip install "pydantic>=2.0.0" "requests>=2.32.4" "openai>=2.24.0"
displayName: 'Install pure python dependencies'
@@ -147,3 +165,26 @@ steps:
Copy-Item "$(repoRoot)/sdk/python/dist/*" "$destDir/"
Write-Host "Staged wheels:"
Get-ChildItem $destDir | ForEach-Object { Write-Host " $($_.Name)" }
+
+# ── Tests ──
+- ${{ if eq(parameters.isWinML, true) }}:
+ - task: PowerShell@2
+ displayName: 'Install Windows App SDK Runtime'
+ inputs:
+ targetType: 'inline'
+ script: |
+ $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe"
+ $installerPath = "$env:TEMP\windowsappruntimeinstall.exe"
+ Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
+ & $installerPath --quiet --force
+ if ($LASTEXITCODE -ne 0) { throw "Windows App SDK Runtime install failed" }
+ errorActionPreference: 'stop'
+
+- script: pip install coverage pytest>=7.0.0 pytest-timeout>=2.1.0
+ displayName: 'Install test dependencies'
+
+- script: python -m pytest test/ -v
+ displayName: 'Run tests'
+ workingDirectory: $(repoRoot)/sdk/python
+ env:
+ TF_BUILD: 'true'
diff --git a/.pipelines/templates/build-rust-steps.yml b/.pipelines/templates/build-rust-steps.yml
index ed3161e5..c0489f4f 100644
--- a/.pipelines/templates/build-rust-steps.yml
+++ b/.pipelines/templates/build-rust-steps.yml
@@ -20,6 +20,10 @@ parameters:
type: string
default: '$(Build.ArtifactStagingDirectory)/rust-sdk'
displayName: 'Path to directory for the packaged crate'
+- name: depsVersionsDir
+ type: string
+ default: ''
+ displayName: 'Path to deps-versions artifact directory'
steps:
# Set paths for multi-repo checkout
- task: PowerShell@2
@@ -57,6 +61,24 @@ steps:
Write-Host "Contents of ${{ parameters.flcNugetDir }}:"
Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse | ForEach-Object { Write-Host $_.FullName }
+# Load dependency versions from deps_versions.json
+- template: update-deps-versions-steps.yml
+ parameters:
+ repoRoot: $(repoRoot)
+ artifactDir: ${{ parameters.depsVersionsDir }}
+ isWinML: ${{ parameters.isWinML }}
+
+# Copy both deps_versions JSON files into the crate directory so cargo
+# package includes them and build.rs can find the right one at build time
+# since there is only 1 package for both rust artifacts.
+- task: PowerShell@2
+ displayName: 'Copy deps_versions for crate packaging'
+ inputs:
+ targetType: inline
+ script: |
+ Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" -Force
+ Copy-Item "$(repoRoot)/sdk/deps_versions_winml.json" "$(repoRoot)/sdk/rust/deps_versions_winml.json" -Force
+
# Extract FLC native binaries from the pipeline-built .nupkg so that
# build.rs finds them already present and skips downloading from the feed.
- task: PowerShell@2
@@ -90,7 +112,9 @@ steps:
$flcNativeDir = "$(Build.ArtifactStagingDirectory)/flc-native-rust"
New-Item -ItemType Directory -Path $flcNativeDir -Force | Out-Null
Get-ChildItem $nativeDir -File | Copy-Item -Destination $flcNativeDir -Force
- Write-Host "##vso[task.setvariable variable=flcNativeDir]$flcNativeDir"
+ # Set FOUNDRY_NATIVE_OVERRIDE_DIR so build.rs copies these into OUT_DIR
+ # instead of trying to download the unpublished FLC Core from the feed.
+ Write-Host "##vso[task.setvariable variable=FOUNDRY_NATIVE_OVERRIDE_DIR]$flcNativeDir"
Write-Host "Extracted FLC native binaries to $flcNativeDir`:"
Get-ChildItem $flcNativeDir | ForEach-Object { Write-Host " $($_.Name)" }
@@ -154,28 +178,6 @@ steps:
Invoke-Expression "cargo build $features"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
-# Overwrite the FLC core binary in cargo's OUT_DIR with the pipeline-built
-# version so that integration tests use the freshly-built FLC. build.rs
-# sets FOUNDRY_NATIVE_DIR to OUT_DIR, which the SDK checks at runtime.
-- task: PowerShell@2
- displayName: 'Overwrite FLC binary with pipeline-built version'
- inputs:
- targetType: inline
- script: |
- # Find cargo's OUT_DIR for the foundry-local-sdk build script
- $outDir = Get-ChildItem "$(repoRoot)/sdk/rust/target/debug/build" -Directory -Filter "foundry-local-sdk-*" -Recurse |
- Where-Object { Test-Path "$($_.FullName)/out" } |
- ForEach-Object { "$($_.FullName)/out" } |
- Select-Object -First 1
- if (-not $outDir) { throw "Could not find cargo OUT_DIR for foundry-local-sdk" }
- Write-Host "Cargo OUT_DIR: $outDir"
-
- # Copy pipeline-built FLC native binaries over the downloaded ones
- Get-ChildItem "$(flcNativeDir)" -File -Filter "Microsoft.AI.Foundry.Local.Core.*" | ForEach-Object {
- Copy-Item $_.FullName -Destination "$outDir/$($_.Name)" -Force
- Write-Host "Overwrote $($_.Name) with pipeline-built version"
- }
-
# --allow-dirty allows packaging with uncommitted changes (build.rs modifies generated files)
- task: PowerShell@2
displayName: 'Package crate'
@@ -198,3 +200,26 @@ steps:
Copy-Item "$(repoRoot)/sdk/rust/target/package/*.crate" "$destDir/"
Write-Host "Staged crates:"
Get-ChildItem $destDir | ForEach-Object { Write-Host " $($_.Name)" }
+
+# ── Tests ──
+- task: PowerShell@2
+ displayName: 'Run unit tests'
+ inputs:
+ targetType: inline
+ script: |
+ Set-Location "$(repoRoot)/sdk/rust"
+ $features = if ("${{ parameters.isWinML }}" -eq "True") { "--features winml" } else { "" }
+ Invoke-Expression "cargo test --lib $features"
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+
+- task: PowerShell@2
+ displayName: 'Run integration tests'
+ inputs:
+ targetType: inline
+ script: |
+ Set-Location "$(repoRoot)/sdk/rust"
+ $features = if ("${{ parameters.isWinML }}" -eq "True") { "--features winml" } else { "" }
+ Invoke-Expression "cargo test --tests $features -- --include-ignored --test-threads=1 --nocapture"
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ env:
+ TF_BUILD: 'true'
diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml
index 01697085..fdd54c28 100644
--- a/.pipelines/templates/package-core-steps.yml
+++ b/.pipelines/templates/package-core-steps.yml
@@ -246,3 +246,50 @@ steps:
Write-Host "`nAll wheels:"
Get-ChildItem $stagingDir -Filter "*.whl" | ForEach-Object { Write-Host " $($_.Name)" }
+
+# Write partial deps_versions.json for this variant. The merge_deps_versions
+# stage combines standard + WinML partials into a single complete artifact.
+- task: PowerShell@2
+ displayName: 'Write deps_versions.json artifact'
+ inputs:
+ targetType: inline
+ script: |
+ $nsRoot = "$(nsRoot)"
+ [xml]$propsXml = Get-Content "$nsRoot/Directory.Packages.props"
+ $pg = $propsXml.Project.PropertyGroup
+
+ $isWinML = "${{ parameters.isWinML }}" -eq "True"
+
+ # Compute PEP 440 version from the NuGet flcVersion
+ $parts = "$(flcVersion)" -split '-'
+ $pyVer = if ($parts.Count -ge 3 -and $parts[1] -eq 'dev') { "$($parts[0]).dev$($parts[2])" }
+ elseif ($parts.Count -eq 2) { "$($parts[0])$($parts[1])" }
+ else { $parts[0] }
+
+ # Both standard and WinML write a deps_versions.json with identical key
+ # structure. The pipeline produces separate artifacts (deps-versions-standard
+ # / deps-versions-winml) so SDK stages pick the right one via isWinML.
+ if ($isWinML) {
+ $deps = @{
+ 'foundry-local-core' = @{ nuget = "$(flcVersion)"; python = $pyVer }
+ onnxruntime = @{ version = [string]$pg.OnnxRuntimeFoundryVersionForWinML }
+ 'onnxruntime-genai' = @{ version = [string]$pg.OnnxRuntimeGenAIFoundryVersion }
+ }
+ } else {
+ $deps = @{
+ 'foundry-local-core' = @{ nuget = "$(flcVersion)"; python = $pyVer }
+ onnxruntime = @{ version = [string]$pg.OnnxRuntimeFoundryVersion }
+ 'onnxruntime-genai' = @{ version = [string]$pg.OnnxRuntimeGenAIFoundryVersion }
+ }
+ }
+
+ # WinML artifact is named deps_versions_winml.json to match repo convention.
+ $fileName = if ($isWinML) { "deps_versions_winml.json" } else { "deps_versions.json" }
+ $json = $deps | ConvertTo-Json -Depth 3
+ Write-Host "${fileName}:"
+ Write-Host $json
+
+ $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions"
+ New-Item -ItemType Directory -Path $outDir -Force | Out-Null
+ [System.IO.File]::WriteAllText("$outDir/$fileName", $json, [System.Text.UTF8Encoding]::new($false))
+ Write-Host "Wrote $fileName to $outDir"
diff --git a/.pipelines/templates/test-cs-steps.yml b/.pipelines/templates/test-cs-steps.yml
deleted file mode 100644
index 92c9b6ee..00000000
--- a/.pipelines/templates/test-cs-steps.yml
+++ /dev/null
@@ -1,117 +0,0 @@
-# Lightweight test-only steps for the C# SDK.
-# Builds from source and runs tests — no signing or NuGet packing.
-parameters:
-- name: version
- type: string
-- name: isWinML
- type: boolean
- default: false
-- name: flcNugetDir
- type: string
- displayName: 'Path to directory containing the FLC .nupkg'
-
-steps:
-- task: PowerShell@2
- displayName: 'Set source paths'
- inputs:
- targetType: inline
- script: |
- $repoRoot = "$(Build.SourcesDirectory)/Foundry-Local"
- $testDataDir = "$(Build.SourcesDirectory)/test-data-shared"
- Write-Host "##vso[task.setvariable variable=repoRoot]$repoRoot"
- Write-Host "##vso[task.setvariable variable=testDataDir]$testDataDir"
-
-- task: UseDotNet@2
- displayName: 'Use .NET 9 SDK'
- inputs:
- packageType: sdk
- version: '9.0.x'
-
-- task: PowerShell@2
- displayName: 'List downloaded FLC artifact'
- inputs:
- targetType: inline
- script: |
- Write-Host "Contents of ${{ parameters.flcNugetDir }}:"
- Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse | ForEach-Object { Write-Host $_.FullName }
-
-- ${{ if eq(parameters.isWinML, true) }}:
- - task: PowerShell@2
- displayName: 'Install Windows App SDK Runtime'
- inputs:
- targetType: 'inline'
- script: |
- $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe"
- $installerPath = "$env:TEMP\windowsappruntimeinstall.exe"
-
- Write-Host "Downloading Windows App SDK Runtime installer from $installerUrl..."
- Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
-
- Write-Host "Installing Windows App SDK Runtime..."
- & $installerPath --quiet --force
-
- if ($LASTEXITCODE -ne 0) {
- Write-Error "Installation failed with exit code $LASTEXITCODE"
- exit 1
- }
-
- Write-Host "Windows App SDK Runtime installed successfully."
- errorActionPreference: 'stop'
-
-- task: PowerShell@2
- displayName: 'Create NuGet.config with local FLC feed'
- inputs:
- targetType: inline
- script: |
- $nugetConfig = @"
-
-
-
-
-
-
-
-
-
- "@
- $nupkg = Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse -Filter "Microsoft.AI.Foundry.Local.Core*.nupkg" -Exclude "*.snupkg" | Select-Object -First 1
- if (-not $nupkg) { throw "No FLC .nupkg found in ${{ parameters.flcNugetDir }}" }
- $flcVer = $nupkg.BaseName -replace '^Microsoft\.AI\.Foundry\.Local\.Core(\.WinML)?\.', ''
- Write-Host "##vso[task.setvariable variable=resolvedFlcVersion]$flcVer"
-
- $flcFeedDir = $nupkg.DirectoryName
- $nugetConfig = $nugetConfig -replace [regex]::Escape("${{ parameters.flcNugetDir }}"), $flcFeedDir
- $configPath = "$(Build.ArtifactStagingDirectory)/NuGet.config"
- Set-Content -Path $configPath -Value $nugetConfig
- Write-Host "##vso[task.setvariable variable=customNugetConfig]$configPath"
-
-- task: NuGetAuthenticate@1
- displayName: 'Authenticate NuGet feeds'
-
-- task: PowerShell@2
- displayName: 'Restore & build tests'
- inputs:
- targetType: inline
- script: |
- dotnet restore "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" `
- --configfile "$(customNugetConfig)" `
- /p:UseWinML=${{ parameters.isWinML }} `
- /p:FoundryLocalCoreVersion=$(resolvedFlcVersion)
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
-
- dotnet build "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" `
- --no-restore --configuration Release `
- /p:UseWinML=${{ parameters.isWinML }}
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
-
-- task: PowerShell@2
- displayName: 'Run SDK tests'
- inputs:
- targetType: inline
- script: |
- dotnet test "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" `
- --no-build --configuration Release `
- /p:UseWinML=${{ parameters.isWinML }}
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- env:
- TF_BUILD: 'true'
diff --git a/.pipelines/templates/test-js-steps.yml b/.pipelines/templates/test-js-steps.yml
deleted file mode 100644
index 1814626a..00000000
--- a/.pipelines/templates/test-js-steps.yml
+++ /dev/null
@@ -1,122 +0,0 @@
-# Lightweight test-only steps for the JS SDK.
-# Builds from source and runs tests — no npm pack or artifact staging.
-parameters:
-- name: version
- type: string
-- name: isWinML
- type: boolean
- default: false
-- name: flcNugetDir
- type: string
- displayName: 'Path to directory containing the FLC .nupkg'
-
-steps:
-- task: PowerShell@2
- displayName: 'Set source paths'
- inputs:
- targetType: inline
- script: |
- $repoRoot = "$(Build.SourcesDirectory)/Foundry-Local"
- $testDataDir = "$(Build.SourcesDirectory)/test-data-shared"
- Write-Host "##vso[task.setvariable variable=repoRoot]$repoRoot"
- Write-Host "##vso[task.setvariable variable=testDataDir]$testDataDir"
-
-- ${{ if eq(parameters.isWinML, true) }}:
- - task: PowerShell@2
- displayName: 'Install Windows App SDK Runtime'
- inputs:
- targetType: 'inline'
- script: |
- $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe"
- $installerPath = "$env:TEMP\windowsappruntimeinstall.exe"
-
- Write-Host "Downloading Windows App SDK Runtime installer from $installerUrl..."
- Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
-
- Write-Host "Installing Windows App SDK Runtime..."
- & $installerPath --quiet --force
-
- if ($LASTEXITCODE -ne 0) {
- Write-Error "Installation failed with exit code $LASTEXITCODE"
- exit 1
- }
-
- Write-Host "Windows App SDK Runtime installed successfully."
- errorActionPreference: 'stop'
-
-- task: PowerShell@2
- displayName: 'List downloaded FLC artifact'
- inputs:
- targetType: inline
- script: |
- Write-Host "Contents of ${{ parameters.flcNugetDir }}:"
- Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse | ForEach-Object { Write-Host $_.FullName }
-
-- task: NodeTool@0
- displayName: 'Use Node.js 20'
- inputs:
- versionSpec: '20.x'
-
-- task: Npm@1
- displayName: 'npm install'
- inputs:
- command: custom
- workingDir: $(repoRoot)/sdk/js
- customCommand: 'install'
-
-# Overwrite the FLC native binary with the pipeline-built one
-- task: PowerShell@2
- displayName: 'Overwrite FLC with pipeline-built binary'
- inputs:
- targetType: inline
- script: |
- $os = 'win32'
- $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
- $platformKey = "$os-$arch"
- $rid = if ($arch -eq 'arm64') { 'win-arm64' } else { 'win-x64' }
-
- if ($IsLinux) {
- $os = 'linux'
- $platformKey = "$os-$arch"
- $rid = "linux-$arch"
- } elseif ($IsMacOS) {
- $os = 'darwin'
- $platformKey = "$os-$arch"
- $rid = "osx-$arch"
- }
-
- $nupkg = Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse -Filter "Microsoft.AI.Foundry.Local.Core*.nupkg" -Exclude "*.snupkg" | Select-Object -First 1
- if (-not $nupkg) { throw "No FLC .nupkg found in ${{ parameters.flcNugetDir }}" }
-
- $extractDir = "$(Build.ArtifactStagingDirectory)/flc-extract"
- $zip = [System.IO.Path]::ChangeExtension($nupkg.FullName, ".zip")
- Copy-Item $nupkg.FullName $zip -Force
- Expand-Archive -Path $zip -DestinationPath $extractDir -Force
-
- $destDir = "$(repoRoot)/sdk/js/node_modules/@foundry-local-core/$platformKey"
- New-Item -ItemType Directory -Path $destDir -Force | Out-Null
- $nativeDir = "$extractDir/runtimes/$rid/native"
- if (Test-Path $nativeDir) {
- Get-ChildItem $nativeDir -File | ForEach-Object {
- Copy-Item $_.FullName -Destination "$destDir/$($_.Name)" -Force
- Write-Host "Overwrote $($_.Name) with pipeline-built version"
- }
- } else {
- Write-Warning "No native binaries found at $nativeDir for RID $rid"
- }
-
-- task: Npm@1
- displayName: 'npm build'
- inputs:
- command: custom
- workingDir: $(repoRoot)/sdk/js
- customCommand: 'run build'
-
-- task: Npm@1
- displayName: 'npm test'
- inputs:
- command: custom
- workingDir: $(repoRoot)/sdk/js
- customCommand: 'test'
- env:
- TF_BUILD: 'true'
diff --git a/.pipelines/templates/test-rust-steps.yml b/.pipelines/templates/test-rust-steps.yml
deleted file mode 100644
index 31bfd75e..00000000
--- a/.pipelines/templates/test-rust-steps.yml
+++ /dev/null
@@ -1,159 +0,0 @@
-# Lightweight test-only steps for the Rust SDK.
-# Builds from source and runs tests — no cargo package or artifact staging.
-parameters:
-- name: isWinML
- type: boolean
- default: false
-- name: flcNugetDir
- type: string
- displayName: 'Path to directory containing the FLC .nupkg'
-
-steps:
-- task: PowerShell@2
- displayName: 'Set source paths'
- inputs:
- targetType: inline
- script: |
- $repoRoot = "$(Build.SourcesDirectory)/Foundry-Local"
- $testDataDir = "$(Build.SourcesDirectory)/test-data-shared"
- Write-Host "##vso[task.setvariable variable=repoRoot]$repoRoot"
- Write-Host "##vso[task.setvariable variable=testDataDir]$testDataDir"
-
-- ${{ if eq(parameters.isWinML, true) }}:
- - task: PowerShell@2
- displayName: 'Install Windows App SDK Runtime'
- inputs:
- targetType: 'inline'
- script: |
- $installerUrl = "https://aka.ms/windowsappsdk/1.8/latest/windowsappruntimeinstall-x64.exe"
- $installerPath = "$env:TEMP\windowsappruntimeinstall.exe"
-
- Write-Host "Downloading Windows App SDK Runtime installer from $installerUrl..."
- Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
-
- Write-Host "Installing Windows App SDK Runtime..."
- & $installerPath --quiet --force
-
- if ($LASTEXITCODE -ne 0) {
- Write-Error "Installation failed with exit code $LASTEXITCODE"
- exit 1
- }
-
- Write-Host "Windows App SDK Runtime installed successfully."
- errorActionPreference: 'stop'
-
-- task: PowerShell@2
- displayName: 'List downloaded FLC artifact'
- inputs:
- targetType: inline
- script: |
- Write-Host "Contents of ${{ parameters.flcNugetDir }}:"
- Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse | ForEach-Object { Write-Host $_.FullName }
-
-# Extract FLC native binaries from the pipeline-built .nupkg
-- task: PowerShell@2
- displayName: 'Extract FLC native binaries'
- inputs:
- targetType: inline
- script: |
- $nupkg = Get-ChildItem "${{ parameters.flcNugetDir }}" -Recurse -Filter "Microsoft.AI.Foundry.Local.Core*.nupkg" -Exclude "*.snupkg" | Select-Object -First 1
- if (-not $nupkg) { throw "No FLC .nupkg found in ${{ parameters.flcNugetDir }}" }
-
- $extractDir = "$(Build.ArtifactStagingDirectory)/flc-extract-rust"
- $zip = [System.IO.Path]::ChangeExtension($nupkg.FullName, ".zip")
- Copy-Item $nupkg.FullName $zip -Force
- Expand-Archive -Path $zip -DestinationPath $extractDir -Force
-
- $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
- if ($IsLinux) {
- $rid = "linux-$arch"
- } elseif ($IsMacOS) {
- $rid = "osx-$arch"
- } else {
- $rid = "win-$arch"
- }
-
- $nativeDir = "$extractDir/runtimes/$rid/native"
- if (-not (Test-Path $nativeDir)) { throw "No native binaries found at $nativeDir for RID $rid" }
-
- $flcNativeDir = "$(Build.ArtifactStagingDirectory)/flc-native-rust"
- New-Item -ItemType Directory -Path $flcNativeDir -Force | Out-Null
- Get-ChildItem $nativeDir -File | Copy-Item -Destination $flcNativeDir -Force
- Write-Host "##vso[task.setvariable variable=flcNativeDir]$flcNativeDir"
- Write-Host "Extracted FLC native binaries for $rid"
-
-- task: PowerShell@2
- displayName: 'Install Rust toolchain'
- inputs:
- targetType: inline
- script: |
- if ($IsWindows -or (-not $IsLinux -and -not $IsMacOS)) {
- Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe
- .\rustup-init.exe -y --default-toolchain stable --profile minimal -c clippy,rustfmt
- Remove-Item rustup-init.exe
- $cargoPath = "$env:USERPROFILE\.cargo\bin"
- } else {
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c clippy,rustfmt
- $cargoPath = "$env:HOME/.cargo/bin"
- }
- Write-Host "##vso[task.prependpath]$cargoPath"
-
-- task: PowerShell@2
- displayName: 'Use crates.io directly'
- inputs:
- targetType: inline
- script: |
- $configPath = "$(repoRoot)/sdk/rust/.cargo/config.toml"
- if (Test-Path $configPath) {
- Remove-Item $configPath
- Write-Host "Removed .cargo/config.toml crates-io redirect"
- }
-
-- task: PowerShell@2
- displayName: 'Build'
- inputs:
- targetType: inline
- script: |
- Set-Location "$(repoRoot)/sdk/rust"
- $features = if ("${{ parameters.isWinML }}" -eq "True") { "--features winml" } else { "" }
- Invoke-Expression "cargo build $features"
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
-
-# Overwrite FLC binary with pipeline-built version
-- task: PowerShell@2
- displayName: 'Overwrite FLC binary with pipeline-built version'
- inputs:
- targetType: inline
- script: |
- $outDir = Get-ChildItem "$(repoRoot)/sdk/rust/target/debug/build" -Directory -Filter "foundry-local-sdk-*" -Recurse |
- Where-Object { Test-Path "$($_.FullName)/out" } |
- ForEach-Object { "$($_.FullName)/out" } |
- Select-Object -First 1
- if (-not $outDir) { throw "Could not find cargo OUT_DIR for foundry-local-sdk" }
-
- Get-ChildItem "$(flcNativeDir)" -File -Filter "Microsoft.AI.Foundry.Local.Core.*" | ForEach-Object {
- Copy-Item $_.FullName -Destination "$outDir/$($_.Name)" -Force
- Write-Host "Overwrote $($_.Name) with pipeline-built version"
- }
-
-- task: PowerShell@2
- displayName: 'Run unit tests'
- inputs:
- targetType: inline
- script: |
- Set-Location "$(repoRoot)/sdk/rust"
- $features = if ("${{ parameters.isWinML }}" -eq "True") { "--features winml" } else { "" }
- Invoke-Expression "cargo test --lib $features"
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
-
-- task: PowerShell@2
- displayName: 'Run integration tests'
- inputs:
- targetType: inline
- script: |
- Set-Location "$(repoRoot)/sdk/rust"
- $features = if ("${{ parameters.isWinML }}" -eq "True") { "--features winml" } else { "" }
- Invoke-Expression "cargo test --tests $features -- --include-ignored --test-threads=1 --nocapture"
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- env:
- TF_BUILD: 'true'
diff --git a/.pipelines/templates/update-deps-versions-steps.yml b/.pipelines/templates/update-deps-versions-steps.yml
new file mode 100644
index 00000000..9d489ab7
--- /dev/null
+++ b/.pipelines/templates/update-deps-versions-steps.yml
@@ -0,0 +1,41 @@
+# Shared template to update deps_versions.json / deps_versions_winml.json
+# from pipeline artifacts. Both files use identical key structure — the
+# isWinML parameter determines which file gets overwritten.
+parameters:
+- name: repoRoot
+ type: string
+ default: '$(repoRoot)'
+- name: artifactDir
+ type: string
+ default: ''
+ displayName: 'Path to artifact directory containing pipeline-generated deps_versions JSON'
+- name: isWinML
+ type: boolean
+ default: false
+
+steps:
+- task: PowerShell@2
+ displayName: 'Update deps_versions from pipeline artifact'
+ inputs:
+ targetType: inline
+ script: |
+ $isWinML = "${{ parameters.isWinML }}" -eq "True"
+ $fileName = if ($isWinML) { "deps_versions_winml.json" } else { "deps_versions.json" }
+ $repoJson = "${{ parameters.repoRoot }}/sdk/$fileName"
+ $artifactDir = "${{ parameters.artifactDir }}"
+
+ if ($artifactDir -eq '' -or -not (Test-Path "$artifactDir/$fileName")) {
+ throw "Pipeline-built $fileName not found in artifact directory: $artifactDir"
+ }
+
+ Copy-Item "$artifactDir/$fileName" $repoJson -Force
+ Write-Host "Updated repo $fileName from pipeline artifact at $artifactDir"
+
+ $deps = Get-Content $repoJson -Raw | ConvertFrom-Json
+
+ # Log resolved versions for debugging
+ Write-Host "Dependency versions from ${fileName}:"
+ Write-Host " FLC Core (NuGet): $($deps.'foundry-local-core'.nuget)"
+ Write-Host " FLC Core (Python): $($deps.'foundry-local-core'.python)"
+ Write-Host " OnnxRuntime: $($deps.onnxruntime.version)"
+ Write-Host " GenAI: $($deps.'onnxruntime-genai'.version)"
diff --git a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj
index 26d74ff6..df8fc2cf 100644
--- a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj
+++ b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj
@@ -98,10 +98,15 @@
-
+
+ <_DepsVersionsPath Condition="'$(UseWinML)' == 'true'">$(MSBuildThisFileDirectory)..\..\deps_versions_winml.json
+ <_DepsVersionsPath Condition="'$(UseWinML)' != 'true'">$(MSBuildThisFileDirectory)..\..\deps_versions.json
+ <_DepsVersionsJson>$([System.IO.File]::ReadAllText('$(_DepsVersionsPath)'))
$(FoundryLocalCoreVersion)
- 0.9.0-dev-202603310538-f6efa8d3
- 0.9.0-dev-202603310538-f6efa8d3
+ $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget"\s*:\s*"([^"]+)"').Groups[1].Value)
+ $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget"\s*:\s*"([^"]+)"').Groups[1].Value)
+
True
diff --git a/sdk/deps_versions.json b/sdk/deps_versions.json
new file mode 100644
index 00000000..aa0b3fa3
--- /dev/null
+++ b/sdk/deps_versions.json
@@ -0,0 +1,12 @@
+{
+ "foundry-local-core": {
+ "nuget": "0.9.0-dev-202603310538-f6efa8d3",
+ "python": "0.9.0.dev20260327060216"
+ },
+ "onnxruntime": {
+ "version": "1.24.3"
+ },
+ "onnxruntime-genai": {
+ "version": "0.13.0"
+ }
+}
diff --git a/sdk/deps_versions_winml.json b/sdk/deps_versions_winml.json
new file mode 100644
index 00000000..a4532421
--- /dev/null
+++ b/sdk/deps_versions_winml.json
@@ -0,0 +1,12 @@
+{
+ "foundry-local-core": {
+ "nuget": "0.9.0-dev-202603310538-f6efa8d3",
+ "python": "0.9.0.dev20260331004032"
+ },
+ "onnxruntime": {
+ "version": "1.23.2.3"
+ },
+ "onnxruntime-genai": {
+ "version": "0.13.0"
+ }
+}
diff --git a/sdk/js/script/install-standard.cjs b/sdk/js/script/install-standard.cjs
index 6901766d..cc86a96f 100644
--- a/sdk/js/script/install-standard.cjs
+++ b/sdk/js/script/install-standard.cjs
@@ -5,13 +5,21 @@
'use strict';
+const fs = require('fs');
const os = require('os');
+const path = require('path');
const { NUGET_FEED, ORT_NIGHTLY_FEED, runInstall } = require('./install-utils.cjs');
+// deps_versions.json lives at the package root when published, or at sdk/ in the repo.
+const depsPath = fs.existsSync(path.resolve(__dirname, '..', 'deps_versions.json'))
+ ? path.resolve(__dirname, '..', 'deps_versions.json')
+ : path.resolve(__dirname, '..', '..', 'deps_versions.json');
+const deps = require(depsPath);
+
const ARTIFACTS = [
- { name: 'Microsoft.AI.Foundry.Local.Core', version: '0.9.0-dev-202603310538-f6efa8d3', feed: ORT_NIGHTLY_FEED },
- { name: os.platform() === 'linux' ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Microsoft.ML.OnnxRuntime.Foundry', version: '1.24.4', feed: NUGET_FEED },
- { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: '0.13.1', feed: NUGET_FEED },
+ { name: 'Microsoft.AI.Foundry.Local.Core', version: deps['foundry-local-core'].nuget, feed: ORT_NIGHTLY_FEED },
+ { name: os.platform() === 'linux' ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.version, feed: NUGET_FEED },
+ { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai'].version, feed: ORT_NIGHTLY_FEED },
];
(async () => {
diff --git a/sdk/js/script/install-utils.cjs b/sdk/js/script/install-utils.cjs
index 090a25e3..aa74f4d5 100644
--- a/sdk/js/script/install-utils.cjs
+++ b/sdk/js/script/install-utils.cjs
@@ -106,10 +106,29 @@ async function getBaseAddress(feedUrl) {
return baseAddress.endsWith('/') ? baseAddress : baseAddress + '/';
}
-async function installPackage(artifact, tempDir, binDir) {
+async function installPackage(artifact, tempDir, binDir, skipIfPresent) {
const pkgName = artifact.name;
const pkgVer = artifact.version;
+ // Skip download if this package's main native binary is already present
+ // (e.g. pre-populated by CI from a locally-built artifact).
+ // Callers pass skipIfPresent=false when overriding (e.g. WinML over standard).
+ if (skipIfPresent) {
+ const prefix = os.platform() === 'win32' ? '' : 'lib';
+ let expectedFile;
+ if (pkgName.includes('Foundry.Local.Core')) {
+ expectedFile = `Microsoft.AI.Foundry.Local.Core${EXT}`;
+ } else if (pkgName.includes('OnnxRuntimeGenAI')) {
+ expectedFile = `${prefix}onnxruntime-genai${EXT}`;
+ } else if (pkgName.includes('OnnxRuntime')) {
+ expectedFile = `${prefix}onnxruntime${EXT}`;
+ }
+ if (expectedFile && fs.existsSync(path.join(binDir, expectedFile))) {
+ console.log(` ${pkgName}: already present, skipping download.`);
+ return;
+ }
+ }
+
const baseAddress = await getBaseAddress(artifact.feed);
const nameLower = pkgName.toLowerCase();
const verLower = pkgVer.toLowerCase();
@@ -136,14 +155,16 @@ async function installPackage(artifact, tempDir, binDir) {
console.warn(` No files found for RID ${RID} in ${pkgName}.`);
}
- // Update platform package.json version for Core packages
+ // Overwrite FLC platform package.json so require.resolve can find the package
if (pkgName.startsWith('Microsoft.AI.Foundry.Local.Core')) {
const pkgJsonPath = path.join(binDir, 'package.json');
- if (fs.existsSync(pkgJsonPath)) {
- const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
- pkgJson.version = pkgVer;
- fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
- }
+ const pkgContent = {
+ name: `@foundry-local-core/${platformKey}`,
+ version: pkgVer,
+ description: `Native binaries for Foundry Local SDK (${platformKey})`,
+ private: true
+ };
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgContent, null, 2));
}
}
@@ -153,13 +174,11 @@ async function runInstall(artifacts, options) {
return;
}
- const force = options && options.force;
const binDir = (options && options.binDir) || BIN_DIR;
-
- if (!force && fs.existsSync(binDir) && REQUIRED_FILES.every(f => fs.existsSync(path.join(binDir, f)))) {
- console.log(`[foundry-local] Native libraries already installed.`);
- return;
- }
+ // When a custom binDir is provided (e.g. WinML overriding standard),
+ // don't skip packages whose output files already exist — we need to
+ // overwrite them with the variant's binaries.
+ const skipIfPresent = !(options && options.binDir);
console.log(`[foundry-local] Installing native libraries for ${RID}...`);
fs.mkdirSync(binDir, { recursive: true });
@@ -167,7 +186,7 @@ async function runInstall(artifacts, options) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'foundry-install-'));
try {
for (const artifact of artifacts) {
- await installPackage(artifact, tempDir, binDir);
+ await installPackage(artifact, tempDir, binDir, skipIfPresent);
}
console.log('[foundry-local] Installation complete.');
} finally {
diff --git a/sdk/js/script/install-winml.cjs b/sdk/js/script/install-winml.cjs
index efa2041c..22a2e97a 100644
--- a/sdk/js/script/install-winml.cjs
+++ b/sdk/js/script/install-winml.cjs
@@ -10,18 +10,26 @@
'use strict';
+const fs = require('fs');
const path = require('path');
const { NUGET_FEED, ORT_NIGHTLY_FEED, runInstall } = require('./install-utils.cjs');
+// WinML uses its own deps_versions_winml.json with the same key structure
+// as the standard deps_versions.json — no variant-specific keys needed.
+// deps_versions_winml.json lives at the package root when published, or at sdk/ in the repo.
+const depsPath = fs.existsSync(path.resolve(__dirname, '..', 'deps_versions_winml.json'))
+ ? path.resolve(__dirname, '..', 'deps_versions_winml.json')
+ : path.resolve(__dirname, '..', '..', 'deps_versions_winml.json');
+const deps = require(depsPath);
// Resolve foundry-local-sdk's binary directory
const sdkRoot = path.dirname(require.resolve('foundry-local-sdk/package.json'));
const platformKey = `${process.platform}-${process.arch}`;
const binDir = path.join(sdkRoot, 'node_modules', '@foundry-local-core', platformKey);
const ARTIFACTS = [
- { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: '0.9.0-dev-202603310538-f6efa8d3', feed: ORT_NIGHTLY_FEED },
- { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: '1.23.2.3', feed: NUGET_FEED },
- { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: '0.13.1', feed: NUGET_FEED },
+ { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: deps['foundry-local-core']['nuget'], feed: ORT_NIGHTLY_FEED },
+ { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.version, feed: NUGET_FEED },
+ { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai']['version'], feed: ORT_NIGHTLY_FEED },
];
(async () => {
diff --git a/sdk/js/script/pack.cjs b/sdk/js/script/pack.cjs
index 79a00828..f550043e 100644
--- a/sdk/js/script/pack.cjs
+++ b/sdk/js/script/pack.cjs
@@ -15,6 +15,15 @@ const pkgPath = path.join(__dirname, '..', 'package.json');
const original = fs.readFileSync(pkgPath, 'utf8');
const isWinML = process.argv[2] === 'winml';
+// deps_versions.json lives in the parent sdk/ directory; copy it into the
+// JS package root so that npm pack includes it in the tarball.
+const pkgRoot = path.join(__dirname, '..');
+const depsSource = path.join(pkgRoot, '..', 'deps_versions.json');
+const depsDest = path.join(pkgRoot, 'deps_versions.json');
+const depsWinmlSource = path.join(pkgRoot, '..', 'deps_versions_winml.json');
+const depsWinmlDest = path.join(pkgRoot, 'deps_versions_winml.json');
+const copiedFiles = [];
+
try {
const pkg = JSON.parse(original);
if (isWinML) {
@@ -25,16 +34,28 @@ try {
pkg.dependencies = { 'foundry-local-sdk': pkg.version };
pkg.scripts = { install: 'node script/install-winml.cjs' };
// No dist/ or preinstall needed — the standard SDK provides the JS code
- pkg.files = ['script/install-winml.cjs', 'script/install-utils.cjs'];
+ pkg.files = ['script/install-winml.cjs', 'script/install-utils.cjs', 'deps_versions_winml.json'];
delete pkg.main;
delete pkg.types;
delete pkg.optionalDependencies;
+ if (fs.existsSync(depsWinmlSource) && !fs.existsSync(depsWinmlDest)) {
+ fs.copyFileSync(depsWinmlSource, depsWinmlDest);
+ copiedFiles.push(depsWinmlDest);
+ }
} else {
- pkg.files = ['dist', 'script/install-standard.cjs', 'script/install-utils.cjs', 'script/preinstall.cjs'];
+ pkg.files = ['dist', 'script/install-standard.cjs', 'script/install-utils.cjs', 'script/preinstall.cjs', 'deps_versions.json'];
+ if (fs.existsSync(depsSource) && !fs.existsSync(depsDest)) {
+ fs.copyFileSync(depsSource, depsDest);
+ copiedFiles.push(depsDest);
+ }
}
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
- execSync('npm pack', { cwd: path.join(__dirname, '..'), stdio: 'inherit' });
+ execSync('npm pack', { cwd: pkgRoot, stdio: 'inherit' });
} finally {
// Always restore original package.json
fs.writeFileSync(pkgPath, original);
+ // Clean up copied deps_versions files
+ for (const f of copiedFiles) {
+ if (fs.existsSync(f)) fs.unlinkSync(f);
+ }
}
diff --git a/sdk/js/script/preinstall.cjs b/sdk/js/script/preinstall.cjs
index 8cd953d2..99e805d7 100644
--- a/sdk/js/script/preinstall.cjs
+++ b/sdk/js/script/preinstall.cjs
@@ -35,18 +35,16 @@ for (const platform of ALL_PLATFORMS) {
}
const pkgJsonPath = path.join(dir, 'package.json');
- if (!fs.existsSync(pkgJsonPath)) {
- const pkgContent = {
- name: `@foundry-local-core/${platform.key}`,
- version: "0.0.0", // Placeholder version, will be replaced during script/install-utils.cjs (installPackage())
- description: `Native binaries for Foundry Local SDK (${platform.key})`,
- os: [platform.os],
- cpu: [platform.cpu],
- private: true
- };
- fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgContent, null, 2));
- console.log(` Created skeleton for ${platform.key}`);
- }
+ const pkgContent = {
+ name: `@foundry-local-core/${platform.key}`,
+ version: "0.0.0",
+ description: `Native binaries for Foundry Local SDK (${platform.key})`,
+ os: [platform.os],
+ cpu: [platform.cpu],
+ private: true
+ };
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgContent, null, 2));
+ console.log(` Created skeleton for ${platform.key}`);
}
console.log('[foundry-local] Preinstall complete.');
diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py
index 1bdf6cbb..57e96286 100644
--- a/sdk/python/build_backend.py
+++ b/sdk/python/build_backend.py
@@ -30,8 +30,8 @@
from __future__ import annotations
import contextlib
+import json
import os
-import shutil
from collections.abc import Generator
from pathlib import Path
@@ -44,13 +44,52 @@
_PROJECT_ROOT = Path(__file__).parent
_PYPROJECT = _PROJECT_ROOT / "pyproject.toml"
_REQUIREMENTS = _PROJECT_ROOT / "requirements.txt"
-_REQUIREMENTS_WINML = _PROJECT_ROOT / "requirements-winml.txt"
+_REQUIREMENTS_BASE = _PROJECT_ROOT / "requirements-base.txt"
# The exact string in pyproject.toml to patch for the WinML variant.
_STANDARD_NAME = 'name = "foundry-local-sdk"'
_WINML_NAME = 'name = "foundry-local-sdk-winml"'
+# ---------------------------------------------------------------------------
+# Requirements generation from deps_versions.json
+# ---------------------------------------------------------------------------
+
+
+def _load_deps_versions(*, winml: bool) -> dict:
+ """Load the appropriate deps_versions JSON file.
+
+ Standard and WinML each have their own file with identical key structure,
+ so callers never need variant-specific key names.
+ """
+ filename = "deps_versions_winml.json" if winml else "deps_versions.json"
+ filepath = _PROJECT_ROOT.parent / filename
+ with open(filepath, encoding="utf-8-sig") as f:
+ return json.load(f)
+
+
+def _generate_requirements(*, winml: bool) -> str:
+ """Generate requirements.txt content from base deps + deps_versions.json."""
+ base = _REQUIREMENTS_BASE.read_text(encoding="utf-8").rstrip("\n")
+ deps = _load_deps_versions(winml=winml)
+
+ if winml:
+ requirement_lines = [
+ f"foundry-local-core-winml=={deps['foundry-local-core']['python']}",
+ f"onnxruntime-core=={deps['onnxruntime']['version']}",
+ f"onnxruntime-genai-core=={deps['onnxruntime-genai']['version']}",
+ ]
+ else:
+ requirement_lines = [
+ f"foundry-local-core=={deps['foundry-local-core']['python']}",
+ f"""onnxruntime-gpu=={deps['onnxruntime']['version']}; platform_system == "Linux" """.rstrip(),
+ f"""onnxruntime-core=={deps['onnxruntime']['version']}; platform_system != "Linux" """.rstrip(),
+ f"""onnxruntime-genai-cuda=={deps['onnxruntime-genai']['version']}; platform_system == "Linux" """.rstrip(),
+ f"""onnxruntime-genai-core=={deps['onnxruntime-genai']['version']}; platform_system != "Linux" """.rstrip(),
+ ]
+ return f"{base}\n" + "\n".join(requirement_lines) + "\n"
+
+
# ---------------------------------------------------------------------------
# Variant detection
# ---------------------------------------------------------------------------
@@ -74,13 +113,12 @@ def _is_winml(config_settings: dict | None) -> bool:
@contextlib.contextmanager
def _patch_for_winml() -> Generator[None, None, None]:
- """Temporarily patch ``pyproject.toml`` and ``requirements.txt`` for WinML.
+ """Temporarily patch ``pyproject.toml`` and generate ``requirements.txt`` for WinML.
- Both files are restored to their original content in the ``finally``
- block, even if the build raises an exception.
+ ``pyproject.toml`` is restored in the ``finally`` block.
+ ``requirements.txt`` is left in place (generated from deps_versions.json).
"""
pyproject_original = _PYPROJECT.read_text(encoding="utf-8")
- requirements_original = _REQUIREMENTS.read_text(encoding="utf-8")
try:
# Patch package name (simple string replacement — no TOML writer needed)
patched_pyproject = pyproject_original.replace(_STANDARD_NAME, _WINML_NAME, 1)
@@ -90,21 +128,24 @@ def _patch_for_winml() -> Generator[None, None, None]:
"WinML name patch failed."
)
_PYPROJECT.write_text(patched_pyproject, encoding="utf-8")
-
- # Swap requirements.txt with the WinML variant
- shutil.copy2(_REQUIREMENTS_WINML, _REQUIREMENTS)
-
+ _REQUIREMENTS.write_text(_generate_requirements(winml=True), encoding="utf-8")
yield
finally:
_PYPROJECT.write_text(pyproject_original, encoding="utf-8")
- _REQUIREMENTS.write_text(requirements_original, encoding="utf-8")
+
+
+@contextlib.contextmanager
+def _patch_standard_deps() -> Generator[None, None, None]:
+ """Generate ``requirements.txt`` from base deps + ``deps_versions.json``."""
+ _REQUIREMENTS.write_text(_generate_requirements(winml=False), encoding="utf-8")
+ yield
def _apply_patches(config_settings: dict | None):
"""Return a context manager that applies the appropriate patches."""
if _is_winml(config_settings):
return _patch_for_winml()
- return contextlib.nullcontext()
+ return _patch_standard_deps()
# ---------------------------------------------------------------------------
@@ -148,7 +189,5 @@ def get_requires_for_build_sdist(config_settings=None):
def build_sdist(sdist_directory, config_settings=None):
- if _is_winml(config_settings):
- with _patch_for_winml():
- return _sb.build_sdist(sdist_directory, config_settings)
- return _sb.build_sdist(sdist_directory, config_settings)
+ with _apply_patches(config_settings):
+ return _sb.build_sdist(sdist_directory, config_settings)
diff --git a/sdk/python/requirements-base.txt b/sdk/python/requirements-base.txt
new file mode 100644
index 00000000..dfc8d718
--- /dev/null
+++ b/sdk/python/requirements-base.txt
@@ -0,0 +1,3 @@
+pydantic>=2.0.0
+requests>=2.32.4
+openai>=2.24.0
diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml
index 2a6292b7..af6a64f2 100644
--- a/sdk/rust/Cargo.toml
+++ b/sdk/rust/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "foundry-local-sdk"
-version = "0.1.0"
+version = "1.0.0"
edition = "2021"
license = "MIT"
readme = "README.md"
@@ -8,6 +8,7 @@ description = "Local AI model inference powered by the Foundry Local Core engine
homepage = "https://www.foundrylocal.ai/"
repository = "https://github.com/microsoft/Foundry-Local"
documentation = "https://github.com/microsoft/Foundry-Local/blob/main/sdk/rust/docs/api.md"
+include = ["src/**", "build.rs", "Cargo.toml", "README.md", "LICENSE", "deps_versions.json", "deps_versions_winml.json"]
[features]
default = []
diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs
index 999bca3d..7daf7a73 100644
--- a/sdk/rust/build.rs
+++ b/sdk/rust/build.rs
@@ -7,11 +7,64 @@ const NUGET_FEED: &str = "https://api.nuget.org/v3/index.json";
const ORT_NIGHTLY_FEED: &str =
"https://pkgs.dev.azure.com/aiinfra/PublicPackages/_packaging/ORT-Nightly/nuget/v3/index.json";
-const CORE_VERSION: &str = "0.9.0.8-rc3";
-const ORT_VERSION: &str = "1.24.4";
-const GENAI_VERSION: &str = "0.13.1";
+/// Versions loaded from deps_versions.json (or deps_versions_winml.json).
+/// Both files share the same key structure — the build script picks the
+/// right file based on the winml cargo feature.
+struct DepsVersions {
+ core: String,
+ ort: String,
+ genai: String,
+}
+
+fn load_deps_versions() -> DepsVersions {
+ let winml = env::var("CARGO_FEATURE_WINML").is_ok();
+ let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
+ let manifest_path = Path::new(&manifest_dir);
+
+ // Standard and WinML each have their own file with identical key structure.
+ let filename = if winml {
+ "deps_versions_winml.json"
+ } else {
+ "deps_versions.json"
+ };
+
+ // Check manifest dir first (packaged crate), then parent (repo layout)
+ let json_path = if manifest_path.join(filename).exists() {
+ manifest_path.join(filename)
+ } else {
+ manifest_path.join("..").join(filename)
+ };
+
+ // Tell Cargo to rebuild if the versions file changes
+ println!(
+ "cargo:rerun-if-changed={}",
+ json_path
+ .canonicalize()
+ .unwrap_or(json_path.clone())
+ .display()
+ );
-const WINML_ORT_VERSION: &str = "1.23.2.3";
+ let content = fs::read_to_string(&json_path).expect("Failed to read deps_versions.json");
+ // Strip UTF-8 BOM if present (PowerShell may write files with BOM)
+ let stripped_content = content.strip_prefix('\u{FEFF}').unwrap_or(&content);
+ let val: serde_json::Value =
+ serde_json::from_str(stripped_content).expect("Failed to parse deps_versions.json");
+
+ let s = |obj: &serde_json::Value, key: &str| -> String {
+ obj.get(key)
+ .and_then(|v| v.as_str())
+ .unwrap_or("")
+ .to_string()
+ };
+ let flc = &val["foundry-local-core"];
+ let ort = &val["onnxruntime"];
+ let genai = &val["onnxruntime-genai"];
+ DepsVersions {
+ core: s(flc, "nuget"),
+ ort: s(ort, "version"),
+ genai: s(genai, "version"),
+ }
+}
struct NuGetPackage {
name: &'static str,
@@ -43,6 +96,7 @@ fn native_lib_extension() -> &'static str {
fn get_packages(rid: &str) -> Vec {
let winml = env::var("CARGO_FEATURE_WINML").is_ok();
let is_linux = rid.starts_with("linux");
+ let deps = load_deps_versions();
// Use pinned versions directly — dynamic resolution via resolve_latest_version
// is unreliable (feed returns versions in unexpected order, and some old versions
@@ -53,44 +107,44 @@ fn get_packages(rid: &str) -> Vec {
if winml {
packages.push(NuGetPackage {
name: "Microsoft.AI.Foundry.Local.Core.WinML",
- version: CORE_VERSION.to_string(),
+ version: deps.core.clone(),
feed_url: ORT_NIGHTLY_FEED,
});
packages.push(NuGetPackage {
name: "Microsoft.ML.OnnxRuntime.Foundry",
- version: WINML_ORT_VERSION.to_string(),
+ version: deps.ort.clone(),
feed_url: NUGET_FEED,
});
packages.push(NuGetPackage {
name: "Microsoft.ML.OnnxRuntimeGenAI.Foundry",
- version: GENAI_VERSION.to_string(),
- feed_url: NUGET_FEED,
+ version: deps.genai.clone(),
+ feed_url: ORT_NIGHTLY_FEED,
});
} else {
packages.push(NuGetPackage {
name: "Microsoft.AI.Foundry.Local.Core",
- version: CORE_VERSION.to_string(),
+ version: deps.core.clone(),
feed_url: ORT_NIGHTLY_FEED,
});
if is_linux {
packages.push(NuGetPackage {
name: "Microsoft.ML.OnnxRuntime.Gpu.Linux",
- version: ORT_VERSION.to_string(),
+ version: deps.ort.clone(),
feed_url: NUGET_FEED,
});
} else {
packages.push(NuGetPackage {
name: "Microsoft.ML.OnnxRuntime.Foundry",
- version: ORT_VERSION.to_string(),
+ version: deps.ort.clone(),
feed_url: NUGET_FEED,
});
}
packages.push(NuGetPackage {
name: "Microsoft.ML.OnnxRuntimeGenAI.Foundry",
- version: GENAI_VERSION.to_string(),
- feed_url: NUGET_FEED,
+ version: deps.genai.clone(),
+ feed_url: ORT_NIGHTLY_FEED,
});
}
@@ -133,7 +187,33 @@ fn resolve_base_address(feed_url: &str) -> Result {
}
/// Download a .nupkg and extract native libraries for the given RID into `out_dir`.
+/// Skips download if native files from this package are already present.
fn download_and_extract(pkg: &NuGetPackage, rid: &str, out_dir: &Path) -> Result<(), String> {
+ // Skip if this package's main native library is already in out_dir
+ // (e.g. pre-populated from FOUNDRY_NATIVE_OVERRIDE_DIR).
+ let ext = native_lib_extension();
+ let prefix = if env::consts::OS == "windows" {
+ ""
+ } else {
+ "lib"
+ };
+ let expected_file = if pkg.name.contains("Foundry.Local.Core") {
+ format!("Microsoft.AI.Foundry.Local.Core.{ext}")
+ } else if pkg.name.contains("OnnxRuntimeGenAI") {
+ format!("{prefix}onnxruntime-genai.{ext}")
+ } else if pkg.name.contains("OnnxRuntime") {
+ format!("{prefix}onnxruntime.{ext}")
+ } else {
+ String::new()
+ };
+ if !expected_file.is_empty() && out_dir.join(&expected_file).exists() {
+ println!(
+ "cargo:warning={} already present, skipping download.",
+ pkg.name
+ );
+ return Ok(());
+ }
+
let base_address = resolve_base_address(pkg.feed_url)?;
let lower_name = pkg.name.to_lowercase();
let lower_version = pkg.version.to_lowercase();
@@ -212,19 +292,26 @@ fn download_and_extract(pkg: &NuGetPackage, rid: &str, out_dir: &Path) -> Result
Ok(())
}
-/// Check whether the core native library is already present in `out_dir`.
+/// Check whether all required native libraries are already present in `out_dir`.
fn libs_already_present(out_dir: &Path) -> bool {
- let core_lib = match env::consts::OS {
- "windows" => "Microsoft.AI.Foundry.Local.Core.dll",
- "linux" => "Microsoft.AI.Foundry.Local.Core.so",
- "macos" => "Microsoft.AI.Foundry.Local.Core.dylib",
- _ => return false,
+ let ext = native_lib_extension();
+ let prefix = if env::consts::OS == "windows" {
+ ""
+ } else {
+ "lib"
};
- out_dir.join(core_lib).exists()
+ let required = [
+ format!("Microsoft.AI.Foundry.Local.Core.{ext}"),
+ format!("{prefix}onnxruntime.{ext}"),
+ format!("{prefix}onnxruntime-genai.{ext}"),
+ ];
+ required.iter().all(|f| out_dir.join(f).exists())
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-env-changed=FOUNDRY_NATIVE_OVERRIDE_DIR");
+ println!("cargo:rerun-if-env-changed=CARGO_FEATURE_WINML");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
@@ -240,7 +327,29 @@ fn main() {
}
};
- // Skip download if libraries already exist
+ // If FOUNDRY_NATIVE_OVERRIDE_DIR is set (e.g. by CI), copy all native
+ // libraries from that directory into OUT_DIR. This pre-populates FLC Core
+ // binaries that aren't published to a feed yet. The download loop below
+ // will then only fetch packages whose files are still missing (ORT, GenAI).
+ if let Ok(override_dir) = env::var("FOUNDRY_NATIVE_OVERRIDE_DIR") {
+ let src = Path::new(&override_dir);
+ if src.is_dir() {
+ let ext = native_lib_extension();
+ for entry in fs::read_dir(src).expect("Failed to read FOUNDRY_NATIVE_OVERRIDE_DIR") {
+ let path = entry.expect("Failed to read dir entry").path();
+ if path.extension().and_then(|e| e.to_str()) == Some(ext) {
+ let dest = out_dir.join(path.file_name().unwrap());
+ fs::copy(&path, &dest).expect("Failed to copy native lib from override dir");
+ println!(
+ "cargo:warning=Copied {} from override dir",
+ path.file_name().unwrap().to_string_lossy()
+ );
+ }
+ }
+ }
+ }
+
+ // Skip all downloads if every required library is already present
if libs_already_present(&out_dir) {
println!("cargo:warning=Native libraries already present in OUT_DIR, skipping download.");
println!("cargo:rustc-link-search=native={}", out_dir.display());
@@ -252,16 +361,22 @@ fn main() {
let packages = get_packages(rid);
+ let mut download_failed = false;
for pkg in &packages {
if let Err(e) = download_and_extract(pkg, rid, &out_dir) {
println!("cargo:warning=Error downloading {}: {e}", pkg.name);
- println!("cargo:warning=Build will continue, but runtime loading may fail.");
- println!(
- "cargo:warning=You can manually place native libraries in the output directory."
- );
+ download_failed = true;
}
}
+ if download_failed && !libs_already_present(&out_dir) {
+ panic!(
+ "One or more native library downloads failed and required libraries are missing. \
+ You can manually place native libraries in the output directory: {}",
+ out_dir.display()
+ );
+ }
+
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-env=FOUNDRY_NATIVE_DIR={}", out_dir.display());