From 73e5515d0ab341b16f922079a11086bd910d6604 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Sat, 4 Apr 2026 23:06:42 -0700 Subject: [PATCH 01/24] impl --- .pipelines/foundry-local-packaging.yml | 70 +++++++++++++++++++ .pipelines/templates/build-cs-steps.yml | 18 +++-- .pipelines/templates/build-js-steps.yml | 11 +++ .pipelines/templates/build-python-steps.yml | 29 ++++++-- .pipelines/templates/build-rust-steps.yml | 11 +++ .pipelines/templates/package-core-steps.yml | 59 ++++++++++++++++ .pipelines/templates/test-cs-steps.yml | 16 +++-- .pipelines/templates/test-js-steps.yml | 11 +++ .pipelines/templates/test-python-steps.yml | 29 ++++++-- .pipelines/templates/test-rust-steps.yml | 11 +++ .../templates/update-deps-versions-steps.yml | 49 +++++++++++++ sdk/cs/src/Microsoft.AI.Foundry.Local.csproj | 9 ++- sdk/deps_versions.json | 18 +++++ sdk/js/script/install-standard.cjs | 8 ++- sdk/js/script/install-winml.cjs | 8 ++- sdk/python/build_backend.py | 58 ++++++++++++--- sdk/python/requirements-base.txt | 3 + sdk/python/requirements-winml.txt | 7 -- sdk/python/requirements.txt | 7 -- sdk/rust/Cargo.toml | 3 +- sdk/rust/build.rs | 61 +++++++++++++--- 21 files changed, 430 insertions(+), 66 deletions(-) create mode 100644 .pipelines/templates/update-deps-versions-steps.yml create mode 100644 sdk/deps_versions.json create mode 100644 sdk/python/requirements-base.txt delete mode 100644 sdk/python/requirements-winml.txt delete mode 100644 sdk/python/requirements.txt diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index 2cb9ee2a..5cae9992 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -167,6 +167,9 @@ extends: - output: pipelineArtifact artifactName: 'flc-wheels' targetPath: '$(Build.ArtifactStagingDirectory)/flc-wheels' + - output: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions' steps: - checkout: neutron-server clean: true @@ -232,6 +235,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'cs-sdk' @@ -248,6 +254,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # ── Build JS SDK ── - stage: build_js @@ -264,6 +271,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'js-sdk' @@ -280,6 +290,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # ── Build Python SDK ── - stage: build_python @@ -296,6 +307,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-wheels' targetPath: '$(Pipeline.Workspace)/flc-wheels' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'python-sdk' @@ -312,6 +326,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # ── Build Rust SDK ── - stage: build_rust @@ -328,6 +343,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'rust-sdk' @@ -344,6 +362,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # ── Test C# SDK (win-x64) ── - stage: test_cs @@ -360,6 +379,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -370,6 +392,7 @@ extends: version: ${{ parameters.version }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # 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. @@ -390,6 +413,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -400,6 +426,7 @@ extends: version: ${{ parameters.version }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # 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. @@ -420,6 +447,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-wheels' targetPath: '$(Pipeline.Workspace)/flc-wheels' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -430,6 +460,7 @@ extends: version: ${{ parameters.version }} isWinML: false flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # 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. @@ -450,6 +481,9 @@ extends: - input: pipelineArtifact artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' + - input: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -459,6 +493,7 @@ extends: parameters: isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # 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. @@ -527,6 +562,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 @@ -578,6 +616,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' @@ -594,6 +635,7 @@ 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) ── @@ -611,6 +653,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' @@ -627,6 +672,7 @@ 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 @@ -643,6 +689,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' @@ -659,6 +708,7 @@ 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) ── @@ -676,6 +726,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: 'rust-sdk-winml' @@ -692,6 +745,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' outputDir: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml' # ── Test C# SDK WinML (win-x64) ── @@ -709,6 +763,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' steps: - checkout: self clean: true @@ -719,6 +776,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -737,6 +795,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' steps: - checkout: self clean: true @@ -747,6 +808,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -765,6 +827,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' steps: - checkout: self clean: true @@ -775,6 +840,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -793,6 +859,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' steps: - checkout: self clean: true @@ -802,6 +871,7 @@ extends: parameters: isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. diff --git a/.pipelines/templates/build-cs-steps.yml b/.pipelines/templates/build-cs-steps.yml index 978c2fff..9a296c3c 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 @@ -55,6 +59,13 @@ steps: Write-Host "##vso[task.setvariable variable=packageVersion]$v" Write-Host "Package version: $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' @@ -81,14 +92,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" diff --git a/.pipelines/templates/build-js-steps.yml b/.pipelines/templates/build-js-steps.yml index e288bbce..d473593d 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,6 +49,13 @@ steps: inputs: versionSpec: '20.x' +# 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' diff --git a/.pipelines/templates/build-python-steps.yml b/.pipelines/templates/build-python-steps.yml index f21d9508..1cd8fae2 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' @@ -110,12 +121,18 @@ 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.12.1 - displayName: 'Install ORT native packages (WinML)' -- ${{ else }}: - - script: pip install onnxruntime-core==1.24.3 onnxruntime-genai-core==0.12.1 - displayName: 'Install ORT native packages' +- task: PowerShell@2 + displayName: 'Install ORT native packages' + inputs: + targetType: inline + script: | + $deps = Get-Content "$(repoRoot)/sdk/deps_versions.json" -Raw | ConvertFrom-Json + $isWinML = "${{ parameters.isWinML }}" -eq "True" + $ortVer = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } + $genaiVer = if ($isWinML) { $deps.'onnxruntime-genai'.'python-winml' } else { $deps.'onnxruntime-genai'.python } + 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' diff --git a/.pipelines/templates/build-rust-steps.yml b/.pipelines/templates/build-rust-steps.yml index efccfaa4..d177d829 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 @@ -64,6 +68,13 @@ 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 }} + # 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 diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml index e5755a21..228d0a58 100644 --- a/.pipelines/templates/package-core-steps.yml +++ b/.pipelines/templates/package-core-steps.yml @@ -254,3 +254,62 @@ steps: Write-Host "`nAll wheels:" Get-ChildItem $stagingDir -Filter "*.whl" | ForEach-Object { Write-Host " $($_.Name)" } + +# Write deps_versions.json as a separate artifact so downstream SDK +# build/test stages can read exact dependency versions. +- 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" + + # Read the committed deps_versions.json as the base + $selfRoot = "$(Build.SourcesDirectory)/Foundry-Local" + $jsonPath = "$selfRoot/sdk/deps_versions.json" + if (-not (Test-Path $jsonPath)) { + # Fallback: self checkout may be under a different name during package_core + # (which checks out neutron-server as the primary repo). Use the committed + # defaults from the JSON structure. + $deps = @{ + 'foundry-local-core' = @{ nuget = ""; 'nuget-winml' = ""; python = ""; 'python-winml' = "" } + onnxruntime = @{ 'cross-plat' = ""; winml = "" } + 'onnxruntime-genai' = @{ nuget = ""; 'nuget-winml' = ""; python = ""; 'python-winml' = "" } + } + } else { + $deps = Get-Content $jsonPath -Raw | ConvertFrom-Json + } + + # 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] } + + # Update with the versions from this build + if ($isWinML) { + $deps.'foundry-local-core'.'nuget-winml' = "$(flcVersion)" + $deps.'foundry-local-core'.'python-winml' = $pyVer + $deps.onnxruntime.winml = [string]$pg.OnnxRuntimeFoundryVersionForWinML + $deps.'onnxruntime-genai'.'nuget-winml' = [string]$pg.OnnxRuntimeGenAIWinML + # Python GenAI WinML version may differ from NuGet; keep committed value + # unless it can be derived from the props file. + } else { + $deps.'foundry-local-core'.nuget = "$(flcVersion)" + $deps.'foundry-local-core'.python = $pyVer + $deps.onnxruntime.'cross-plat' = [string]$pg.OnnxRuntimeFoundryVersion + $deps.'onnxruntime-genai'.nuget = [string]$pg.OnnxRuntimeGenAIFoundryVersion + } + + $json = $deps | ConvertTo-Json -Depth 3 + Write-Host "deps_versions.json contents:" + Write-Host $json + + $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" + New-Item -ItemType Directory -Path $outDir -Force | Out-Null + Set-Content -Path "$outDir/deps_versions.json" -Value $json -Encoding utf8 + Write-Host "Wrote deps_versions.json to $outDir" diff --git a/.pipelines/templates/test-cs-steps.yml b/.pipelines/templates/test-cs-steps.yml index f7dc1aff..98554105 100644 --- a/.pipelines/templates/test-cs-steps.yml +++ b/.pipelines/templates/test-cs-steps.yml @@ -9,6 +9,10 @@ parameters: - name: flcNugetDir type: string displayName: 'Path to directory containing the FLC .nupkg' +- name: depsVersionsDir + type: string + default: '' + displayName: 'Path to deps-versions artifact directory' steps: - task: PowerShell@2 @@ -35,6 +39,13 @@ 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 }} + - ${{ if eq(parameters.isWinML, true) }}: - task: PowerShell@2 displayName: 'Install Windows App SDK Runtime' @@ -75,8 +86,6 @@ steps: "@ $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 @@ -94,8 +103,7 @@ steps: script: | dotnet restore "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" ` --configfile "$(customNugetConfig)" ` - /p:UseWinML=${{ parameters.isWinML }} ` - /p:FoundryLocalCoreVersion=$(resolvedFlcVersion) + /p:UseWinML=${{ parameters.isWinML }} if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } dotnet build "$(repoRoot)/sdk/cs/test/FoundryLocal.Tests/Microsoft.AI.Foundry.Local.Tests.csproj" ` diff --git a/.pipelines/templates/test-js-steps.yml b/.pipelines/templates/test-js-steps.yml index 41ef7f62..37bd55e9 100644 --- a/.pipelines/templates/test-js-steps.yml +++ b/.pipelines/templates/test-js-steps.yml @@ -9,6 +9,10 @@ parameters: - name: flcNugetDir type: string displayName: 'Path to directory containing the FLC .nupkg' +- name: depsVersionsDir + type: string + default: '' + displayName: 'Path to deps-versions artifact directory' steps: - task: PowerShell@2 @@ -57,6 +61,13 @@ steps: inputs: versionSpec: '20.x' +# Load dependency versions from deps_versions.json +- template: update-deps-versions-steps.yml + parameters: + repoRoot: $(repoRoot) + artifactDir: ${{ parameters.depsVersionsDir }} + isWinML: ${{ parameters.isWinML }} + - task: Npm@1 displayName: 'npm install' inputs: diff --git a/.pipelines/templates/test-python-steps.yml b/.pipelines/templates/test-python-steps.yml index 6fc86b3b..01a3d9f6 100644 --- a/.pipelines/templates/test-python-steps.yml +++ b/.pipelines/templates/test-python-steps.yml @@ -9,6 +9,10 @@ parameters: - name: flcWheelsDir type: string displayName: 'Path to directory containing the FLC wheels' +- name: depsVersionsDir + type: string + default: '' + displayName: 'Path to deps-versions artifact directory' steps: - task: PowerShell@2 @@ -49,6 +53,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 }} + - task: PowerShell@2 displayName: 'List downloaded FLC wheels' condition: and(succeeded(), ne('${{ parameters.flcWheelsDir }}', '')) @@ -98,12 +109,18 @@ steps: Write-Warning "No FLC wheel found matching $filter" } -- ${{ if eq(parameters.isWinML, true) }}: - - script: pip install onnxruntime-core==1.23.2.3 onnxruntime-genai-core==0.12.1 - displayName: 'Install ORT native packages (WinML)' -- ${{ else }}: - - script: pip install onnxruntime-core==1.24.3 onnxruntime-genai-core==0.12.1 - displayName: 'Install ORT native packages' +- task: PowerShell@2 + displayName: 'Install ORT native packages' + inputs: + targetType: inline + script: | + $deps = Get-Content "$(repoRoot)/sdk/deps_versions.json" -Raw | ConvertFrom-Json + $isWinML = "${{ parameters.isWinML }}" -eq "True" + $ortVer = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } + $genaiVer = if ($isWinML) { $deps.'onnxruntime-genai'.'python-winml' } else { $deps.'onnxruntime-genai'.python } + 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' diff --git a/.pipelines/templates/test-rust-steps.yml b/.pipelines/templates/test-rust-steps.yml index 31bfd75e..c7d86c8c 100644 --- a/.pipelines/templates/test-rust-steps.yml +++ b/.pipelines/templates/test-rust-steps.yml @@ -7,6 +7,10 @@ parameters: - name: flcNugetDir type: string displayName: 'Path to directory containing the FLC .nupkg' +- name: depsVersionsDir + type: string + default: '' + displayName: 'Path to deps-versions artifact directory' steps: - task: PowerShell@2 @@ -50,6 +54,13 @@ 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 }} + # Extract FLC native binaries from the pipeline-built .nupkg - task: PowerShell@2 displayName: 'Extract FLC native binaries' diff --git a/.pipelines/templates/update-deps-versions-steps.yml b/.pipelines/templates/update-deps-versions-steps.yml new file mode 100644 index 00000000..c3b5366c --- /dev/null +++ b/.pipelines/templates/update-deps-versions-steps.yml @@ -0,0 +1,49 @@ +# Shared template to update deps_versions.json from pipeline artifacts. +# If an artifact directory containing a pipeline-generated deps_versions.json +# is provided, it overwrites the repo copy so that SDK builds use the +# exact versions from the FLC build. Each SDK reads deps_versions.json +# directly via its own tooling (MSBuild, Node require(), serde_json, etc.). +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.json from pipeline artifact' + inputs: + targetType: inline + script: | + $repoJson = "${{ parameters.repoRoot }}/sdk/deps_versions.json" + $artifactDir = "${{ parameters.artifactDir }}" + + if ($artifactDir -eq '' -or -not (Test-Path "$artifactDir/deps_versions.json")) { + throw "Pipeline-built deps_versions.json not found in artifact directory: $artifactDir" + } + + Copy-Item "$artifactDir/deps_versions.json" $repoJson -Force + Write-Host "Updated repo deps_versions.json from pipeline artifact at $artifactDir" + + $deps = Get-Content $repoJson -Raw | ConvertFrom-Json + $isWinML = "${{ parameters.isWinML }}" -eq "True" + + # Log resolved versions for debugging + $flcNuget = if ($isWinML) { $deps.'foundry-local-core'.'nuget-winml' } else { $deps.'foundry-local-core'.nuget } + $flcPython = if ($isWinML) { $deps.'foundry-local-core'.'python-winml' } else { $deps.'foundry-local-core'.python } + $ort = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } + $genaiNuget = if ($isWinML) { $deps.'onnxruntime-genai'.'nuget-winml' } else { $deps.'onnxruntime-genai'.nuget } + $genaiPython = if ($isWinML) { $deps.'onnxruntime-genai'.'python-winml' } else { $deps.'onnxruntime-genai'.python } + + Write-Host "Dependency versions from deps_versions.json:" + Write-Host " FLC Core (NuGet): $flcNuget" + Write-Host " FLC Core (Python): $flcPython" + Write-Host " OnnxRuntime: $ort" + Write-Host " GenAI (NuGet): $genaiNuget" + Write-Host " GenAI (Python): $genaiPython" diff --git a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj index e8a7b755..b44ac0c7 100644 --- a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -98,10 +98,13 @@ - + + <_DepsVersionsJson>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\..\deps_versions.json')) $(FoundryLocalCoreVersion) - 0.9.0-dev-202603310538-f6efa8d3 - 0.9.0-dev-202603310538-f6efa8d3 + $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget-winml"\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..f865469c --- /dev/null +++ b/sdk/deps_versions.json @@ -0,0 +1,18 @@ +{ + "foundry-local-core": { + "nuget": "0.9.0-dev-202603310538-f6efa8d3", + "nuget-winml": "0.9.0-dev-202603310538-f6efa8d3", + "python": "0.9.0.dev20260327060216", + "python-winml": "0.9.0.dev20260331004032" + }, + "onnxruntime": { + "cross-plat": "1.24.3", + "winml": "1.23.2.3" + }, + "onnxruntime-genai": { + "nuget": "0.13.0-dev-20260319-1131106-439ca0d5", + "nuget-winml": "0.13.0-dev-20260319-1131106-439ca0d5", + "python": "0.12.1", + "python-winml": "0.12.1" + } +} diff --git a/sdk/js/script/install-standard.cjs b/sdk/js/script/install-standard.cjs index bd0558b5..ad2b59bd 100644 --- a/sdk/js/script/install-standard.cjs +++ b/sdk/js/script/install-standard.cjs @@ -6,14 +6,16 @@ 'use strict'; const os = require('os'); +const path = require('path'); const { NUGET_FEED, ORT_NIGHTLY_FEED, runInstall } = require('./install-utils.cjs'); const useNightly = process.env.npm_config_nightly === 'true'; +const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); const ARTIFACTS = [ - { name: 'Microsoft.AI.Foundry.Local.Core', version: '0.9.0-dev-202603310538-f6efa8d3', feed: ORT_NIGHTLY_FEED, nightly: useNightly }, - { name: os.platform() === 'linux' ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Microsoft.ML.OnnxRuntime.Foundry', version: '1.24.3', feed: NUGET_FEED, nightly: false }, - { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: '0.13.0-dev-20260319-1131106-439ca0d5', feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.AI.Foundry.Local.Core', version: deps['foundry-local-core'].nuget, feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: os.platform() === 'linux' ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime['cross-plat'], feed: NUGET_FEED, nightly: false }, + { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai'].nuget, feed: ORT_NIGHTLY_FEED, nightly: useNightly }, ]; (async () => { diff --git a/sdk/js/script/install-winml.cjs b/sdk/js/script/install-winml.cjs index dd0cb0d0..a731c71f 100644 --- a/sdk/js/script/install-winml.cjs +++ b/sdk/js/script/install-winml.cjs @@ -5,14 +5,16 @@ 'use strict'; +const path = require('path'); const { NUGET_FEED, ORT_NIGHTLY_FEED, runInstall } = require('./install-utils.cjs'); const useNightly = process.env.npm_config_nightly === 'true'; +const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); const ARTIFACTS = [ - { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: '0.9.0-dev-202603310538-f6efa8d3', feed: ORT_NIGHTLY_FEED, nightly: useNightly }, - { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: '1.23.2.3', feed: NUGET_FEED, nightly: false }, - { name: 'Microsoft.ML.OnnxRuntimeGenAI.WinML', version: '0.13.0-dev-20260319-1131106-439ca0d5', feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: deps['foundry-local-core']['nuget-winml'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.winml, feed: NUGET_FEED, nightly: false }, + { name: 'Microsoft.ML.OnnxRuntimeGenAI.WinML', version: deps['onnxruntime-genai']['nuget-winml'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, ]; (async () => { diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index 1bdf6cbb..e065e080 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,42 @@ _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" +_DEPS_VERSIONS = _PROJECT_ROOT.parent / "deps_versions.json" # 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() -> dict: + """Load deps_versions.json.""" + with open(_DEPS_VERSIONS, encoding="utf-8") 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() + + if winml: + flc = f"foundry-local-core-winml=={deps['foundry-local-core']['python-winml']}" + ort = f"onnxruntime-core=={deps['onnxruntime']['winml']}" + genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['python-winml']}" + else: + flc = f"foundry-local-core=={deps['foundry-local-core']['python']}" + ort = f"onnxruntime-core=={deps['onnxruntime']['cross-plat']}" + genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['python']}" + + return f"{base}\n{flc}\n{ort}\n{genai}\n" + + # --------------------------------------------------------------------------- # Variant detection # --------------------------------------------------------------------------- @@ -91,8 +120,8 @@ def _patch_for_winml() -> Generator[None, None, None]: ) _PYPROJECT.write_text(patched_pyproject, encoding="utf-8") - # Swap requirements.txt with the WinML variant - shutil.copy2(_REQUIREMENTS_WINML, _REQUIREMENTS) + # Generate requirements from base + WinML deps + _REQUIREMENTS.write_text(_generate_requirements(winml=True), encoding="utf-8") yield finally: @@ -100,11 +129,24 @@ def _patch_for_winml() -> Generator[None, None, None]: _REQUIREMENTS.write_text(requirements_original, encoding="utf-8") +@contextlib.contextmanager +def _patch_standard_deps() -> Generator[None, None, None]: + """Temporarily generate ``requirements.txt`` from base deps + + ``deps_versions.json`` for the standard (non-WinML) build. + """ + requirements_original = _REQUIREMENTS.read_text(encoding="utf-8") + try: + _REQUIREMENTS.write_text(_generate_requirements(winml=False), encoding="utf-8") + yield + finally: + _REQUIREMENTS.write_text(requirements_original, encoding="utf-8") + + 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 +190,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/python/requirements-winml.txt b/sdk/python/requirements-winml.txt deleted file mode 100644 index e554890c..00000000 --- a/sdk/python/requirements-winml.txt +++ /dev/null @@ -1,7 +0,0 @@ -pydantic>=2.0.0 -requests>=2.32.4 -openai>=2.24.0 -# WinML native binary packages from the ORT-Nightly PyPI feed. -foundry-local-core-winml==0.9.0.dev20260331004032 -onnxruntime-core==1.23.2.3 -onnxruntime-genai-core==0.12.1 \ No newline at end of file diff --git a/sdk/python/requirements.txt b/sdk/python/requirements.txt deleted file mode 100644 index 801f577d..00000000 --- a/sdk/python/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -pydantic>=2.0.0 -requests>=2.32.4 -openai>=2.24.0 -# Standard native binary packages from the ORT-Nightly PyPI feed. -foundry-local-core==0.9.0.dev20260327060216 -onnxruntime-core==1.24.3 -onnxruntime-genai-core==0.12.1 \ No newline at end of file diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index 2a6292b7..df580fb8 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"] [features] default = [] diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 996eaf2a..262ce66e 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -7,11 +7,49 @@ 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.3"; -const GENAI_VERSION: &str = "0.13.0-dev-20260319-1131106-439ca0d5"; +/// Versions loaded from deps_versions.json. +struct DepsVersions { + core: String, + core_winml: String, + ort: String, + ort_winml: String, + genai: String, + genai_winml: String, +} + +fn load_deps_versions() -> DepsVersions { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_default(); + let json_path = Path::new(&manifest_dir).join("../../deps_versions.json"); + + // 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"); + let val: serde_json::Value = serde_json::from_str(&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"), + core_winml: s(flc, "nuget-winml"), + ort: s(ort, "cross-plat"), + ort_winml: s(ort, "winml"), + genai: s(genai, "nuget"), + genai_winml: s(genai, "nuget-winml"), + } +} struct NuGetPackage { name: &'static str, @@ -43,6 +81,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,43 +92,43 @@ 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_winml.clone(), feed_url: ORT_NIGHTLY_FEED, }); packages.push(NuGetPackage { name: "Microsoft.ML.OnnxRuntime.Foundry", - version: WINML_ORT_VERSION.to_string(), + version: deps.ort_winml.clone(), feed_url: NUGET_FEED, }); packages.push(NuGetPackage { name: "Microsoft.ML.OnnxRuntimeGenAI.WinML", - version: GENAI_VERSION.to_string(), + version: deps.genai_winml.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(), + version: deps.genai.clone(), feed_url: ORT_NIGHTLY_FEED, }); } From 2dbfc48310d1397f09f0a87190373a5bcc73aaeb Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Sat, 4 Apr 2026 23:22:59 -0700 Subject: [PATCH 02/24] github actions bug fixes --- sdk/python/build_backend.py | 23 ++++++----------------- sdk/rust/build.rs | 12 +++++++----- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index e065e080..920a0c74 100644 --- a/sdk/python/build_backend.py +++ b/sdk/python/build_backend.py @@ -103,13 +103,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) @@ -119,27 +118,17 @@ def _patch_for_winml() -> Generator[None, None, None]: "WinML name patch failed." ) _PYPROJECT.write_text(patched_pyproject, encoding="utf-8") - - # Generate requirements from base + WinML deps _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]: - """Temporarily generate ``requirements.txt`` from base deps + - ``deps_versions.json`` for the standard (non-WinML) build. - """ - requirements_original = _REQUIREMENTS.read_text(encoding="utf-8") - try: - _REQUIREMENTS.write_text(_generate_requirements(winml=False), encoding="utf-8") - yield - finally: - _REQUIREMENTS.write_text(requirements_original, encoding="utf-8") + """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): diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 262ce66e..4f055650 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -24,13 +24,15 @@ fn load_deps_versions() -> DepsVersions { // Tell Cargo to rebuild if the versions file changes println!( "cargo:rerun-if-changed={}", - json_path.canonicalize().unwrap_or(json_path.clone()).display() + json_path + .canonicalize() + .unwrap_or(json_path.clone()) + .display() ); - let content = fs::read_to_string(&json_path) - .expect("Failed to read deps_versions.json"); - let val: serde_json::Value = serde_json::from_str(&content) - .expect("Failed to parse deps_versions.json"); + let content = fs::read_to_string(&json_path).expect("Failed to read deps_versions.json"); + let val: serde_json::Value = + serde_json::from_str(&content).expect("Failed to parse deps_versions.json"); let s = |obj: &serde_json::Value, key: &str| -> String { obj.get(key) From 6769477c458cc0b4f76a77d03d16f13e9a40bd2c Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Sat, 4 Apr 2026 23:26:22 -0700 Subject: [PATCH 03/24] rust fix --- sdk/rust/Cargo.toml | 2 +- sdk/rust/build.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index df580fb8..e0b69da5 100644 --- a/sdk/rust/Cargo.toml +++ b/sdk/rust/Cargo.toml @@ -8,7 +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"] +include = ["src/**", "build.rs", "Cargo.toml", "README.md", "LICENSE", "../deps_versions.json"] [features] default = [] diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 4f055650..48a3089e 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -19,7 +19,7 @@ struct DepsVersions { fn load_deps_versions() -> DepsVersions { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_default(); - let json_path = Path::new(&manifest_dir).join("../../deps_versions.json"); + let json_path = Path::new(&manifest_dir).join("../deps_versions.json"); // Tell Cargo to rebuild if the versions file changes println!( From e632ac413cbcd7b0df5d8001a2bb937e9e84f8f2 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Sat, 4 Apr 2026 23:31:38 -0700 Subject: [PATCH 04/24] rust fix v2 --- .github/workflows/build-rust-steps.yml | 6 ++++++ .pipelines/templates/build-rust-steps.yml | 8 ++++++++ .pipelines/templates/test-rust-steps.yml | 7 +++++++ sdk/rust/Cargo.toml | 2 +- sdk/rust/build.rs | 9 ++++++++- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-rust-steps.yml b/.github/workflows/build-rust-steps.yml index f007b7ee..27f8b110 100644 --- a/.github/workflows/build-rust-steps.yml +++ b/.github/workflows/build-rust-steps.yml @@ -58,6 +58,12 @@ 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 + 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/templates/build-rust-steps.yml b/.pipelines/templates/build-rust-steps.yml index d177d829..0c94fc9a 100644 --- a/.pipelines/templates/build-rust-steps.yml +++ b/.pipelines/templates/build-rust-steps.yml @@ -75,6 +75,14 @@ steps: artifactDir: ${{ parameters.depsVersionsDir }} isWinML: ${{ parameters.isWinML }} +# Copy deps_versions.json into the crate directory so cargo package +# can include it and build.rs can find it during verify. +- task: PowerShell@2 + displayName: 'Copy deps_versions.json for crate packaging' + inputs: + targetType: inline + script: Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" + # 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 diff --git a/.pipelines/templates/test-rust-steps.yml b/.pipelines/templates/test-rust-steps.yml index c7d86c8c..bcc4d1da 100644 --- a/.pipelines/templates/test-rust-steps.yml +++ b/.pipelines/templates/test-rust-steps.yml @@ -61,6 +61,13 @@ steps: artifactDir: ${{ parameters.depsVersionsDir }} isWinML: ${{ parameters.isWinML }} +# Copy deps_versions.json into the crate directory so build.rs can find it. +- task: PowerShell@2 + displayName: 'Copy deps_versions.json for Rust build' + inputs: + targetType: inline + script: Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" + # Extract FLC native binaries from the pipeline-built .nupkg - task: PowerShell@2 displayName: 'Extract FLC native binaries' diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index e0b69da5..eddb7907 100644 --- a/sdk/rust/Cargo.toml +++ b/sdk/rust/Cargo.toml @@ -8,7 +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"] +include = ["src/**", "build.rs", "Cargo.toml", "README.md", "LICENSE", "deps_versions.json"] [features] default = [] diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 48a3089e..3399005e 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -19,7 +19,14 @@ struct DepsVersions { fn load_deps_versions() -> DepsVersions { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_default(); - let json_path = Path::new(&manifest_dir).join("../deps_versions.json"); + let manifest_path = Path::new(&manifest_dir); + + // Check manifest dir first (packaged crate), then parent (repo layout) + let json_path = if manifest_path.join("deps_versions.json").exists() { + manifest_path.join("deps_versions.json") + } else { + manifest_path.join("../deps_versions.json") + }; // Tell Cargo to rebuild if the versions file changes println!( From 59e9a842f64047fb2c3a520a45d4efb8fc1a0f28 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Sat, 4 Apr 2026 23:34:52 -0700 Subject: [PATCH 05/24] rust fix v3 --- .github/workflows/build-rust-steps.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-rust-steps.yml b/.github/workflows/build-rust-steps.yml index 27f8b110..810b6c1e 100644 --- a/.github/workflows/build-rust-steps.yml +++ b/.github/workflows/build-rust-steps.yml @@ -62,6 +62,7 @@ jobs: # 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 From d2c9ebf2f7c5fddaf638d1ae45daf39698c6fd2c Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Sun, 5 Apr 2026 00:51:45 -0700 Subject: [PATCH 06/24] merge deps versions --- .pipelines/foundry-local-packaging.yml | 149 +++++++++++++++----- .pipelines/templates/package-core-steps.yml | 43 ++---- 2 files changed, 129 insertions(+), 63 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index 5cae9992..56677acb 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -168,7 +168,7 @@ extends: artifactName: 'flc-wheels' targetPath: '$(Build.ArtifactStagingDirectory)/flc-wheels' - output: pipelineArtifact - artifactName: 'deps-versions' + artifactName: 'deps-versions-standard' targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions' steps: - checkout: neutron-server @@ -223,7 +223,9 @@ extends: # ── Build C# SDK ── - stage: build_cs displayName: 'Build C# SDK' - dependsOn: package_core + dependsOn: + - package_core + - merge_deps_versions jobs: - job: cs_sdk displayName: 'Build' @@ -259,7 +261,9 @@ extends: # ── Build JS SDK ── - stage: build_js displayName: 'Build JS SDK' - dependsOn: package_core + dependsOn: + - package_core + - merge_deps_versions jobs: - job: js_sdk displayName: 'Build' @@ -295,7 +299,9 @@ extends: # ── Build Python SDK ── - stage: build_python displayName: 'Build Python SDK' - dependsOn: package_core + dependsOn: + - package_core + - merge_deps_versions jobs: - job: python_sdk displayName: 'Build' @@ -331,7 +337,9 @@ extends: # ── Build Rust SDK ── - stage: build_rust displayName: 'Build Rust SDK' - dependsOn: package_core + dependsOn: + - package_core + - merge_deps_versions jobs: - job: rust_sdk displayName: 'Build' @@ -601,10 +609,79 @@ extends: - name: win-arm64 artifactName: flc-winml-win-arm64 + # ── Merge deps_versions.json ── + # Both packaging stages write partial JSON (standard or WinML keys only). + # This stage merges them into a single complete deps_versions.json artifact + # that all SDK build/test stages consume. + - stage: merge_deps_versions + displayName: 'Merge Dependency Versions' + dependsOn: + - package_core + - package_core_winml + jobs: + - job: merge + displayName: 'Merge deps_versions.json' + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + outputs: + - output: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions' + steps: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + - task: PowerShell@2 + displayName: 'Merge standard + WinML deps_versions.json' + inputs: + targetType: inline + script: | + $standard = Get-Content "$(Pipeline.Workspace)/deps-versions-standard/deps_versions.json" -Raw | ConvertFrom-Json + $winml = Get-Content "$(Pipeline.Workspace)/deps-versions-winml/deps_versions.json" -Raw | ConvertFrom-Json + + # Merge: take all keys from both partials + $merged = @{ + 'foundry-local-core' = @{ + nuget = $standard.'foundry-local-core'.nuget + 'nuget-winml' = $winml.'foundry-local-core'.'nuget-winml' + python = $standard.'foundry-local-core'.python + 'python-winml' = $winml.'foundry-local-core'.'python-winml' + } + onnxruntime = @{ + 'cross-plat' = $standard.onnxruntime.'cross-plat' + winml = $winml.onnxruntime.winml + } + 'onnxruntime-genai' = @{ + nuget = $standard.'onnxruntime-genai'.nuget + 'nuget-winml' = $winml.'onnxruntime-genai'.'nuget-winml' + python = $standard.'onnxruntime-genai'.python + 'python-winml' = $winml.'onnxruntime-genai'.'python-winml' + } + } + + $json = $merged | ConvertTo-Json -Depth 3 + Write-Host "Merged deps_versions.json:" + Write-Host $json + + $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" + New-Item -ItemType Directory -Path $outDir -Force | Out-Null + Set-Content -Path "$outDir/deps_versions.json" -Value $json -Encoding utf8 + # ── Build C# SDK (WinML) ── - stage: build_cs_winml displayName: 'Build C# SDK WinML' - dependsOn: package_core_winml + dependsOn: + - package_core_winml + - merge_deps_versions jobs: - job: cs_sdk_winml displayName: 'Build' @@ -617,8 +694,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'cs-sdk-winml' @@ -635,13 +712,15 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' outputDir: '$(Build.ArtifactStagingDirectory)/cs-sdk-winml' # ── Build JS SDK (WinML) ── - stage: build_js_winml displayName: 'Build JS SDK WinML' - dependsOn: package_core_winml + dependsOn: + - package_core_winml + - merge_deps_versions jobs: - job: js_sdk_winml displayName: 'Build' @@ -654,8 +733,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'js-sdk-winml' @@ -672,12 +751,14 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # ── Build Python SDK (WinML) ── - stage: build_python_winml displayName: 'Build Python SDK WinML' - dependsOn: package_core_winml + dependsOn: + - package_core_winml + - merge_deps_versions jobs: - job: python_sdk_winml displayName: 'Build' @@ -690,8 +771,8 @@ extends: artifactName: 'flc-wheels-winml' targetPath: '$(Pipeline.Workspace)/flc-wheels-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'python-sdk-winml' @@ -708,13 +789,15 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk-winml' # ── Build Rust SDK (WinML) ── - stage: build_rust_winml displayName: 'Build Rust SDK WinML' - dependsOn: package_core_winml + dependsOn: + - package_core_winml + - merge_deps_versions jobs: - job: rust_sdk_winml displayName: 'Build' @@ -727,8 +810,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' outputs: - output: pipelineArtifact artifactName: 'rust-sdk-winml' @@ -745,7 +828,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' outputDir: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml' # ── Test C# SDK WinML (win-x64) ── @@ -764,8 +847,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -776,7 +859,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -796,8 +879,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -808,7 +891,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -828,8 +911,8 @@ extends: artifactName: 'flc-wheels-winml' targetPath: '$(Pipeline.Workspace)/flc-wheels-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -840,7 +923,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -860,8 +943,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + artifactName: 'deps-versions' + targetPath: '$(Pipeline.Workspace)/deps-versions' steps: - checkout: self clean: true @@ -871,7 +954,7 @@ extends: parameters: isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml index 228d0a58..58f94c5c 100644 --- a/.pipelines/templates/package-core-steps.yml +++ b/.pipelines/templates/package-core-steps.yml @@ -255,8 +255,8 @@ steps: Write-Host "`nAll wheels:" Get-ChildItem $stagingDir -Filter "*.whl" | ForEach-Object { Write-Host " $($_.Name)" } -# Write deps_versions.json as a separate artifact so downstream SDK -# build/test stages can read exact dependency versions. +# 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: @@ -268,45 +268,28 @@ steps: $isWinML = "${{ parameters.isWinML }}" -eq "True" - # Read the committed deps_versions.json as the base - $selfRoot = "$(Build.SourcesDirectory)/Foundry-Local" - $jsonPath = "$selfRoot/sdk/deps_versions.json" - if (-not (Test-Path $jsonPath)) { - # Fallback: self checkout may be under a different name during package_core - # (which checks out neutron-server as the primary repo). Use the committed - # defaults from the JSON structure. - $deps = @{ - 'foundry-local-core' = @{ nuget = ""; 'nuget-winml' = ""; python = ""; 'python-winml' = "" } - onnxruntime = @{ 'cross-plat' = ""; winml = "" } - 'onnxruntime-genai' = @{ nuget = ""; 'nuget-winml' = ""; python = ""; 'python-winml' = "" } - } - } else { - $deps = Get-Content $jsonPath -Raw | ConvertFrom-Json - } - # 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] } - # Update with the versions from this build if ($isWinML) { - $deps.'foundry-local-core'.'nuget-winml' = "$(flcVersion)" - $deps.'foundry-local-core'.'python-winml' = $pyVer - $deps.onnxruntime.winml = [string]$pg.OnnxRuntimeFoundryVersionForWinML - $deps.'onnxruntime-genai'.'nuget-winml' = [string]$pg.OnnxRuntimeGenAIWinML - # Python GenAI WinML version may differ from NuGet; keep committed value - # unless it can be derived from the props file. + $deps = @{ + 'foundry-local-core' = @{ 'nuget-winml' = "$(flcVersion)"; 'python-winml' = $pyVer } + onnxruntime = @{ winml = [string]$pg.OnnxRuntimeFoundryVersionForWinML } + 'onnxruntime-genai' = @{ 'nuget-winml' = [string]$pg.OnnxRuntimeGenAIWinML; 'python-winml' = [string]$pg.OnnxRuntimeGenAIWinML } + } } else { - $deps.'foundry-local-core'.nuget = "$(flcVersion)" - $deps.'foundry-local-core'.python = $pyVer - $deps.onnxruntime.'cross-plat' = [string]$pg.OnnxRuntimeFoundryVersion - $deps.'onnxruntime-genai'.nuget = [string]$pg.OnnxRuntimeGenAIFoundryVersion + $deps = @{ + 'foundry-local-core' = @{ nuget = "$(flcVersion)"; python = $pyVer } + onnxruntime = @{ 'cross-plat' = [string]$pg.OnnxRuntimeFoundryVersion } + 'onnxruntime-genai' = @{ nuget = [string]$pg.OnnxRuntimeGenAIFoundryVersion; python = [string]$pg.OnnxRuntimeGenAIFoundryVersion } + } } $json = $deps | ConvertTo-Json -Depth 3 - Write-Host "deps_versions.json contents:" + Write-Host "deps_versions.json (partial — $( if ($isWinML) { 'WinML' } else { 'standard' } )):" Write-Host $json $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" From c35ce35e639e3a2c13021a0ee4dac6248d4b0b04 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Mon, 6 Apr 2026 10:34:52 -0700 Subject: [PATCH 07/24] checkout none --- .pipelines/foundry-local-packaging.yml | 134 ++++++++++++------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index 56677acb..fdc4a5ce 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -609,73 +609,6 @@ extends: - name: win-arm64 artifactName: flc-winml-win-arm64 - # ── Merge deps_versions.json ── - # Both packaging stages write partial JSON (standard or WinML keys only). - # This stage merges them into a single complete deps_versions.json artifact - # that all SDK build/test stages consume. - - stage: merge_deps_versions - displayName: 'Merge Dependency Versions' - dependsOn: - - package_core - - package_core_winml - jobs: - - job: merge - displayName: 'Merge deps_versions.json' - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - outputs: - - output: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions' - steps: - - task: DownloadPipelineArtifact@2 - inputs: - buildType: current - artifactName: 'deps-versions-standard' - targetPath: '$(Pipeline.Workspace)/deps-versions-standard' - - task: DownloadPipelineArtifact@2 - inputs: - buildType: current - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' - - task: PowerShell@2 - displayName: 'Merge standard + WinML deps_versions.json' - inputs: - targetType: inline - script: | - $standard = Get-Content "$(Pipeline.Workspace)/deps-versions-standard/deps_versions.json" -Raw | ConvertFrom-Json - $winml = Get-Content "$(Pipeline.Workspace)/deps-versions-winml/deps_versions.json" -Raw | ConvertFrom-Json - - # Merge: take all keys from both partials - $merged = @{ - 'foundry-local-core' = @{ - nuget = $standard.'foundry-local-core'.nuget - 'nuget-winml' = $winml.'foundry-local-core'.'nuget-winml' - python = $standard.'foundry-local-core'.python - 'python-winml' = $winml.'foundry-local-core'.'python-winml' - } - onnxruntime = @{ - 'cross-plat' = $standard.onnxruntime.'cross-plat' - winml = $winml.onnxruntime.winml - } - 'onnxruntime-genai' = @{ - nuget = $standard.'onnxruntime-genai'.nuget - 'nuget-winml' = $winml.'onnxruntime-genai'.'nuget-winml' - python = $standard.'onnxruntime-genai'.python - 'python-winml' = $winml.'onnxruntime-genai'.'python-winml' - } - } - - $json = $merged | ConvertTo-Json -Depth 3 - Write-Host "Merged deps_versions.json:" - Write-Host $json - - $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" - New-Item -ItemType Directory -Path $outDir -Force | Out-Null - Set-Content -Path "$outDir/deps_versions.json" -Value $json -Encoding utf8 - # ── Build C# SDK (WinML) ── - stage: build_cs_winml displayName: 'Build C# SDK WinML' @@ -958,3 +891,70 @@ extends: # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. + # ── Merge deps_versions.json ── + # Both packaging stages write partial JSON (standard or WinML keys only). + # This stage merges them into a single complete deps_versions.json artifact + # that all SDK build/test stages consume. + - stage: merge_deps_versions + displayName: 'Merge Dependency Versions' + dependsOn: + - package_core + - package_core_winml + jobs: + - job: merge + displayName: 'Merge deps_versions.json' + pool: + name: onnxruntime-Win-CPU-2022 + os: windows + templateContext: + outputs: + - output: pipelineArtifact + artifactName: 'deps-versions' + targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions' + steps: + - checkout: none + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' + - task: PowerShell@2 + displayName: 'Merge standard + WinML deps_versions.json' + inputs: + targetType: inline + script: | + $standard = Get-Content "$(Pipeline.Workspace)/deps-versions-standard/deps_versions.json" -Raw | ConvertFrom-Json + $winml = Get-Content "$(Pipeline.Workspace)/deps-versions-winml/deps_versions.json" -Raw | ConvertFrom-Json + + # Merge: take all keys from both partials + $merged = @{ + 'foundry-local-core' = @{ + nuget = $standard.'foundry-local-core'.nuget + 'nuget-winml' = $winml.'foundry-local-core'.'nuget-winml' + python = $standard.'foundry-local-core'.python + 'python-winml' = $winml.'foundry-local-core'.'python-winml' + } + onnxruntime = @{ + 'cross-plat' = $standard.onnxruntime.'cross-plat' + winml = $winml.onnxruntime.winml + } + 'onnxruntime-genai' = @{ + nuget = $standard.'onnxruntime-genai'.nuget + 'nuget-winml' = $winml.'onnxruntime-genai'.'nuget-winml' + python = $standard.'onnxruntime-genai'.python + 'python-winml' = $winml.'onnxruntime-genai'.'python-winml' + } + } + + $json = $merged | ConvertTo-Json -Depth 3 + Write-Host "Merged deps_versions.json:" + Write-Host $json + + $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" + New-Item -ItemType Directory -Path $outDir -Force | Out-Null + Set-Content -Path "$outDir/deps_versions.json" -Value $json -Encoding utf8 \ No newline at end of file From 14ab58d85810c4a34cdd2f93c1d13d72fdbd7d40 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Mon, 6 Apr 2026 11:06:00 -0700 Subject: [PATCH 08/24] rust fmt fix --- sdk/rust/build.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index ac315e95..3399005e 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -110,15 +110,9 @@ fn get_packages(rid: &str) -> Vec { feed_url: NUGET_FEED, }); packages.push(NuGetPackage { -<<<<<<< HEAD name: "Microsoft.ML.OnnxRuntimeGenAI.WinML", version: deps.genai_winml.clone(), feed_url: ORT_NIGHTLY_FEED, -======= - name: "Microsoft.ML.OnnxRuntimeGenAI.Foundry", - version: GENAI_VERSION.to_string(), - feed_url: NUGET_FEED, ->>>>>>> origin }); } else { packages.push(NuGetPackage { @@ -143,13 +137,8 @@ fn get_packages(rid: &str) -> Vec { packages.push(NuGetPackage { name: "Microsoft.ML.OnnxRuntimeGenAI.Foundry", -<<<<<<< HEAD version: deps.genai.clone(), feed_url: ORT_NIGHTLY_FEED, -======= - version: GENAI_VERSION.to_string(), - feed_url: NUGET_FEED, ->>>>>>> origin }); } From 1be045b1e769b8d225f0b915445d275488052fe3 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Mon, 6 Apr 2026 13:16:55 -0700 Subject: [PATCH 09/24] bug fixes --- .pipelines/foundry-local-packaging.yml | 2 +- .pipelines/templates/build-js-steps.yml | 59 +++++++++++++++++++-- .pipelines/templates/package-core-steps.yml | 2 +- sdk/js/script/install-utils.cjs | 8 +++ sdk/python/build_backend.py | 3 +- sdk/rust/build.rs | 4 +- 6 files changed, 71 insertions(+), 7 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index da287fd0..375161ca 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -957,4 +957,4 @@ extends: $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" New-Item -ItemType Directory -Path $outDir -Force | Out-Null - Set-Content -Path "$outDir/deps_versions.json" -Value $json -Encoding utf8 \ No newline at end of file + [System.IO.File]::WriteAllText("$outDir/deps_versions.json", $json, [System.Text.UTF8Encoding]::new($false)) \ No newline at end of file diff --git a/.pipelines/templates/build-js-steps.yml b/.pipelines/templates/build-js-steps.yml index d473593d..7034eb7c 100644 --- a/.pipelines/templates/build-js-steps.yml +++ b/.pipelines/templates/build-js-steps.yml @@ -72,13 +72,66 @@ steps: } 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/packages/@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 diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml index fa16182c..f14f942f 100644 --- a/.pipelines/templates/package-core-steps.yml +++ b/.pipelines/templates/package-core-steps.yml @@ -294,5 +294,5 @@ steps: $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" New-Item -ItemType Directory -Path $outDir -Force | Out-Null - Set-Content -Path "$outDir/deps_versions.json" -Value $json -Encoding utf8 + [System.IO.File]::WriteAllText("$outDir/deps_versions.json", $json, [System.Text.UTF8Encoding]::new($false)) Write-Host "Wrote deps_versions.json to $outDir" diff --git a/sdk/js/script/install-utils.cjs b/sdk/js/script/install-utils.cjs index cc61f0db..b86cf778 100644 --- a/sdk/js/script/install-utils.cjs +++ b/sdk/js/script/install-utils.cjs @@ -108,6 +108,14 @@ async function installPackage(artifact, tempDir) { const pkgName = artifact.name; const pkgVer = artifact.version; + // Skip if this package's native binaries are already present (e.g. pre-populated + // by a CI pipeline from a locally-built artifact). + if (pkgName.startsWith('Microsoft.AI.Foundry.Local.Core') && + fs.existsSync(path.join(BIN_DIR, `Microsoft.AI.Foundry.Local.Core${EXT}`))) { + console.log(` ${pkgName}: already present, skipping download.`); + return; + } + const baseAddress = await getBaseAddress(artifact.feed); const nameLower = pkgName.toLowerCase(); const verLower = pkgVer.toLowerCase(); diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index 920a0c74..3c201f2b 100644 --- a/sdk/python/build_backend.py +++ b/sdk/python/build_backend.py @@ -59,7 +59,8 @@ def _load_deps_versions() -> dict: """Load deps_versions.json.""" - with open(_DEPS_VERSIONS, encoding="utf-8") as f: + # read with utf-8-sig encoding which handles the BOM from PS Set-Content (used in CI pipeline) + with open(_DEPS_VERSIONS, encoding="utf-8-sig") as f: return json.load(f) diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 3399005e..a95178c3 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -38,8 +38,10 @@ fn load_deps_versions() -> DepsVersions { ); 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(&content).expect("Failed to parse deps_versions.json"); + 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) From 20bb8ee33c6dd69af6b4d012add6a4c28677b93b Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Mon, 6 Apr 2026 14:26:01 -0700 Subject: [PATCH 10/24] standard genai --- .pipelines/foundry-local-packaging.yml | 2 -- .pipelines/templates/build-python-steps.yml | 2 +- .pipelines/templates/package-core-steps.yml | 2 +- .pipelines/templates/test-python-steps.yml | 2 +- .pipelines/templates/update-deps-versions-steps.yml | 4 ++-- sdk/deps_versions.json | 6 ++---- sdk/js/script/install-winml.cjs | 2 +- sdk/python/build_backend.py | 2 +- sdk/rust/build.rs | 4 +--- 9 files changed, 10 insertions(+), 16 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index 375161ca..dfe1c5df 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -945,9 +945,7 @@ extends: } 'onnxruntime-genai' = @{ nuget = $standard.'onnxruntime-genai'.nuget - 'nuget-winml' = $winml.'onnxruntime-genai'.'nuget-winml' python = $standard.'onnxruntime-genai'.python - 'python-winml' = $winml.'onnxruntime-genai'.'python-winml' } } diff --git a/.pipelines/templates/build-python-steps.yml b/.pipelines/templates/build-python-steps.yml index 1cd8fae2..de0051f3 100644 --- a/.pipelines/templates/build-python-steps.yml +++ b/.pipelines/templates/build-python-steps.yml @@ -129,7 +129,7 @@ steps: $deps = Get-Content "$(repoRoot)/sdk/deps_versions.json" -Raw | ConvertFrom-Json $isWinML = "${{ parameters.isWinML }}" -eq "True" $ortVer = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } - $genaiVer = if ($isWinML) { $deps.'onnxruntime-genai'.'python-winml' } else { $deps.'onnxruntime-genai'.python } + $genaiVer = $deps.'onnxruntime-genai'.python 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 } diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml index f14f942f..7043f020 100644 --- a/.pipelines/templates/package-core-steps.yml +++ b/.pipelines/templates/package-core-steps.yml @@ -278,7 +278,7 @@ steps: $deps = @{ 'foundry-local-core' = @{ 'nuget-winml' = "$(flcVersion)"; 'python-winml' = $pyVer } onnxruntime = @{ winml = [string]$pg.OnnxRuntimeFoundryVersionForWinML } - 'onnxruntime-genai' = @{ 'nuget-winml' = [string]$pg.OnnxRuntimeGenAIWinML; 'python-winml' = [string]$pg.OnnxRuntimeGenAIWinML } + 'onnxruntime-genai' = @{ nuget = [string]$pg.OnnxRuntimeGenAIWinML; python = [string]$pg.OnnxRuntimeGenAIWinML } } } else { $deps = @{ diff --git a/.pipelines/templates/test-python-steps.yml b/.pipelines/templates/test-python-steps.yml index 01a3d9f6..e18b4de5 100644 --- a/.pipelines/templates/test-python-steps.yml +++ b/.pipelines/templates/test-python-steps.yml @@ -117,7 +117,7 @@ steps: $deps = Get-Content "$(repoRoot)/sdk/deps_versions.json" -Raw | ConvertFrom-Json $isWinML = "${{ parameters.isWinML }}" -eq "True" $ortVer = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } - $genaiVer = if ($isWinML) { $deps.'onnxruntime-genai'.'python-winml' } else { $deps.'onnxruntime-genai'.python } + $genaiVer = $deps.'onnxruntime-genai'.python 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 } diff --git a/.pipelines/templates/update-deps-versions-steps.yml b/.pipelines/templates/update-deps-versions-steps.yml index c3b5366c..0f9ccffa 100644 --- a/.pipelines/templates/update-deps-versions-steps.yml +++ b/.pipelines/templates/update-deps-versions-steps.yml @@ -38,8 +38,8 @@ steps: $flcNuget = if ($isWinML) { $deps.'foundry-local-core'.'nuget-winml' } else { $deps.'foundry-local-core'.nuget } $flcPython = if ($isWinML) { $deps.'foundry-local-core'.'python-winml' } else { $deps.'foundry-local-core'.python } $ort = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } - $genaiNuget = if ($isWinML) { $deps.'onnxruntime-genai'.'nuget-winml' } else { $deps.'onnxruntime-genai'.nuget } - $genaiPython = if ($isWinML) { $deps.'onnxruntime-genai'.'python-winml' } else { $deps.'onnxruntime-genai'.python } + $genaiNuget = $deps.'onnxruntime-genai'.nuget + $genaiPython = $deps.'onnxruntime-genai'.python Write-Host "Dependency versions from deps_versions.json:" Write-Host " FLC Core (NuGet): $flcNuget" diff --git a/sdk/deps_versions.json b/sdk/deps_versions.json index f865469c..a5a5ea2b 100644 --- a/sdk/deps_versions.json +++ b/sdk/deps_versions.json @@ -10,9 +10,7 @@ "winml": "1.23.2.3" }, "onnxruntime-genai": { - "nuget": "0.13.0-dev-20260319-1131106-439ca0d5", - "nuget-winml": "0.13.0-dev-20260319-1131106-439ca0d5", - "python": "0.12.1", - "python-winml": "0.12.1" + "nuget": "0.13.0", + "python": "0.13.0" } } diff --git a/sdk/js/script/install-winml.cjs b/sdk/js/script/install-winml.cjs index a731c71f..baf7c47a 100644 --- a/sdk/js/script/install-winml.cjs +++ b/sdk/js/script/install-winml.cjs @@ -14,7 +14,7 @@ const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); const ARTIFACTS = [ { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: deps['foundry-local-core']['nuget-winml'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.winml, feed: NUGET_FEED, nightly: false }, - { name: 'Microsoft.ML.OnnxRuntimeGenAI.WinML', version: deps['onnxruntime-genai']['nuget-winml'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.ML.OnnxRuntimeGenAI.WinML', version: deps['onnxruntime-genai']['nuget'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, ]; (async () => { diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index 3c201f2b..de059439 100644 --- a/sdk/python/build_backend.py +++ b/sdk/python/build_backend.py @@ -72,7 +72,7 @@ def _generate_requirements(*, winml: bool) -> str: if winml: flc = f"foundry-local-core-winml=={deps['foundry-local-core']['python-winml']}" ort = f"onnxruntime-core=={deps['onnxruntime']['winml']}" - genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['python-winml']}" + genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['python']}" else: flc = f"foundry-local-core=={deps['foundry-local-core']['python']}" ort = f"onnxruntime-core=={deps['onnxruntime']['cross-plat']}" diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index a95178c3..08ab61cf 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -14,7 +14,6 @@ struct DepsVersions { ort: String, ort_winml: String, genai: String, - genai_winml: String, } fn load_deps_versions() -> DepsVersions { @@ -58,7 +57,6 @@ fn load_deps_versions() -> DepsVersions { ort: s(ort, "cross-plat"), ort_winml: s(ort, "winml"), genai: s(genai, "nuget"), - genai_winml: s(genai, "nuget-winml"), } } @@ -113,7 +111,7 @@ fn get_packages(rid: &str) -> Vec { }); packages.push(NuGetPackage { name: "Microsoft.ML.OnnxRuntimeGenAI.WinML", - version: deps.genai_winml.clone(), + version: deps.genai.clone(), feed_url: ORT_NIGHTLY_FEED, }); } else { From bbf0f4bf231f7f12385da1d65c726b47f6f8b4f1 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Mon, 6 Apr 2026 16:41:36 -0700 Subject: [PATCH 11/24] bug fixes --- .pipelines/templates/test-js-steps.yml | 20 ++++++++++++++------ sdk/js/script/install-winml.cjs | 2 +- sdk/rust/build.rs | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.pipelines/templates/test-js-steps.yml b/.pipelines/templates/test-js-steps.yml index 37bd55e9..7f34613e 100644 --- a/.pipelines/templates/test-js-steps.yml +++ b/.pipelines/templates/test-js-steps.yml @@ -69,15 +69,16 @@ steps: isWinML: ${{ parameters.isWinML }} - 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' -# Overwrite the FLC native binary with the pipeline-built one +# Extract FLC from the pipeline-built artifact, then run the install script +# to fetch ORT/GenAI from public feeds (FLC download is skipped since it's already present). - task: PowerShell@2 - displayName: 'Overwrite FLC with pipeline-built binary' + displayName: 'Extract FLC and install native binaries' inputs: targetType: inline script: | @@ -104,17 +105,24 @@ steps: Copy-Item $nupkg.FullName $zip -Force Expand-Archive -Path $zip -DestinationPath $extractDir -Force - $destDir = "$(repoRoot)/sdk/js/packages/@foundry-local-core/$platformKey" + # 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 "Overwrote $($_.Name) with pipeline-built version" + Write-Host "Placed $($_.Name) from pipeline artifact" } } else { Write-Warning "No native binaries found at $nativeDir for RID $rid" } + # Run preinstall + install script to fetch ORT/GenAI (FLC is already present) + Set-Location "$(repoRoot)/sdk/js" + node script/preinstall.cjs + node script/install-standard.cjs + - task: Npm@1 displayName: 'npm build' inputs: diff --git a/sdk/js/script/install-winml.cjs b/sdk/js/script/install-winml.cjs index baf7c47a..d93bfca3 100644 --- a/sdk/js/script/install-winml.cjs +++ b/sdk/js/script/install-winml.cjs @@ -14,7 +14,7 @@ const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); const ARTIFACTS = [ { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: deps['foundry-local-core']['nuget-winml'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.winml, feed: NUGET_FEED, nightly: false }, - { name: 'Microsoft.ML.OnnxRuntimeGenAI.WinML', version: deps['onnxruntime-genai']['nuget'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai']['nuget'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, ]; (async () => { diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 08ab61cf..ee0ef0ed 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -110,7 +110,7 @@ fn get_packages(rid: &str) -> Vec { feed_url: NUGET_FEED, }); packages.push(NuGetPackage { - name: "Microsoft.ML.OnnxRuntimeGenAI.WinML", + name: "Microsoft.ML.OnnxRuntimeGenAI.Foundry", version: deps.genai.clone(), feed_url: ORT_NIGHTLY_FEED, }); From 8b9b6d16f5302cd0b4fef171f984c07b7e44217d Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Mon, 6 Apr 2026 22:31:21 -0700 Subject: [PATCH 12/24] bug fix --- .pipelines/templates/build-js-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/templates/build-js-steps.yml b/.pipelines/templates/build-js-steps.yml index 488042d1..aea262d9 100644 --- a/.pipelines/templates/build-js-steps.yml +++ b/.pipelines/templates/build-js-steps.yml @@ -106,7 +106,7 @@ steps: Expand-Archive -Path $zip -DestinationPath $extractDir -Force # Place FLC binary so the install script skips downloading it - $destDir = "$(repoRoot)/sdk/js/packages/@foundry-local-core/$platformKey" + $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) { From 68dfe94caab1a358cbc13e574daaa3bd224355cb Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 08:28:45 -0700 Subject: [PATCH 13/24] separate deps --- .pipelines/foundry-local-packaging.yml | 169 +++++------------- .pipelines/templates/build-python-steps.yml | 8 +- .pipelines/templates/build-rust-steps.yml | 15 +- .pipelines/templates/package-core-steps.yml | 21 ++- .pipelines/templates/test-python-steps.yml | 8 +- .pipelines/templates/test-rust-steps.yml | 12 +- .../templates/update-deps-versions-steps.yml | 42 ++--- sdk/deps_versions.json | 10 +- sdk/deps_versions_winml.json | 12 ++ sdk/js/script/install-standard.cjs | 4 +- sdk/js/script/install-winml.cjs | 10 +- sdk/python/build_backend.py | 25 +-- sdk/rust/build.rs | 26 +-- 13 files changed, 158 insertions(+), 204 deletions(-) create mode 100644 sdk/deps_versions_winml.json diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index a5b0ce05..a314e181 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -287,7 +287,6 @@ extends: displayName: 'Build C# SDK' dependsOn: - package_core - - merge_deps_versions jobs: - job: cs_sdk displayName: 'Build' @@ -303,8 +302,8 @@ extends: artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' outputs: - output: pipelineArtifact artifactName: 'cs-sdk' @@ -321,14 +320,13 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # ── Build JS SDK ── - stage: build_js displayName: 'Build JS SDK' dependsOn: - package_core - - merge_deps_versions jobs: - job: js_sdk displayName: 'Build' @@ -344,8 +342,8 @@ extends: artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' outputs: - output: pipelineArtifact artifactName: 'js-sdk' @@ -362,14 +360,13 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # ── Build Python SDK ── - stage: build_python displayName: 'Build Python SDK' dependsOn: - package_core - - merge_deps_versions jobs: - job: python_sdk displayName: 'Build' @@ -385,8 +382,8 @@ extends: artifactName: 'flc-wheels' targetPath: '$(Pipeline.Workspace)/flc-wheels' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' outputs: - output: pipelineArtifact artifactName: 'python-sdk' @@ -403,14 +400,13 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # ── Build Rust SDK ── - stage: build_rust displayName: 'Build Rust SDK' dependsOn: - package_core - - merge_deps_versions jobs: - job: rust_sdk displayName: 'Build' @@ -426,8 +422,8 @@ extends: artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' outputs: - output: pipelineArtifact artifactName: 'rust-sdk' @@ -444,7 +440,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # ── Test C# SDK (win-x64) ── - stage: test_cs @@ -462,8 +458,8 @@ extends: artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' steps: - checkout: self clean: true @@ -474,7 +470,7 @@ extends: version: ${{ parameters.version }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # 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. @@ -496,8 +492,8 @@ extends: artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' steps: - checkout: self clean: true @@ -508,7 +504,7 @@ extends: version: ${{ parameters.version }} isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # 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. @@ -530,8 +526,8 @@ extends: artifactName: 'flc-wheels' targetPath: '$(Pipeline.Workspace)/flc-wheels' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' steps: - checkout: self clean: true @@ -542,7 +538,7 @@ extends: version: ${{ parameters.version }} isWinML: false flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # 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. @@ -564,8 +560,8 @@ extends: artifactName: 'flc-nuget' targetPath: '$(Pipeline.Workspace)/flc-nuget' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-standard' + targetPath: '$(Pipeline.Workspace)/deps-versions-standard' steps: - checkout: self clean: true @@ -575,7 +571,7 @@ extends: parameters: isWinML: false flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' # 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. @@ -692,7 +688,6 @@ extends: displayName: 'Build C# SDK WinML' dependsOn: - package_core_winml - - merge_deps_versions jobs: - job: cs_sdk_winml displayName: 'Build' @@ -708,8 +703,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' outputs: - output: pipelineArtifact artifactName: 'cs-sdk-winml' @@ -726,7 +721,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' outputDir: '$(Build.ArtifactStagingDirectory)/cs-sdk-winml' # ── Build JS SDK (WinML) ── @@ -734,7 +729,6 @@ extends: displayName: 'Build JS SDK WinML' dependsOn: - package_core_winml - - merge_deps_versions jobs: - job: js_sdk_winml displayName: 'Build' @@ -750,8 +744,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' outputs: - output: pipelineArtifact artifactName: 'js-sdk-winml' @@ -768,14 +762,13 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # ── Build Python SDK (WinML) ── - stage: build_python_winml displayName: 'Build Python SDK WinML' dependsOn: - package_core_winml - - merge_deps_versions jobs: - job: python_sdk_winml displayName: 'Build' @@ -791,8 +784,8 @@ extends: artifactName: 'flc-wheels-winml' targetPath: '$(Pipeline.Workspace)/flc-wheels-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' outputs: - output: pipelineArtifact artifactName: 'python-sdk-winml' @@ -809,7 +802,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' outputDir: '$(Build.ArtifactStagingDirectory)/python-sdk-winml' # ── Build Rust SDK (WinML) ── @@ -817,7 +810,6 @@ extends: displayName: 'Build Rust SDK WinML' dependsOn: - package_core_winml - - merge_deps_versions jobs: - job: rust_sdk_winml displayName: 'Build' @@ -833,8 +825,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' outputs: - output: pipelineArtifact artifactName: 'rust-sdk-winml' @@ -851,7 +843,7 @@ extends: prereleaseId: ${{ parameters.prereleaseId }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' outputDir: '$(Build.ArtifactStagingDirectory)/rust-sdk-winml' # ── Test C# SDK WinML (win-x64) ── @@ -870,8 +862,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' steps: - checkout: self clean: true @@ -882,7 +874,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -902,8 +894,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' steps: - checkout: self clean: true @@ -914,7 +906,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -934,8 +926,8 @@ extends: artifactName: 'flc-wheels-winml' targetPath: '$(Pipeline.Workspace)/flc-wheels-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' steps: - checkout: self clean: true @@ -946,7 +938,7 @@ extends: version: ${{ parameters.version }} isWinML: true flcWheelsDir: '$(Pipeline.Workspace)/flc-wheels-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. @@ -966,8 +958,8 @@ extends: artifactName: 'flc-nuget-winml' targetPath: '$(Pipeline.Workspace)/flc-nuget-winml' - input: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Pipeline.Workspace)/deps-versions' + artifactName: 'deps-versions-winml' + targetPath: '$(Pipeline.Workspace)/deps-versions-winml' steps: - checkout: self clean: true @@ -977,72 +969,7 @@ extends: parameters: isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions' + depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. - # ── Merge deps_versions.json ── - # Both packaging stages write partial JSON (standard or WinML keys only). - # This stage merges them into a single complete deps_versions.json artifact - # that all SDK build/test stages consume. - - stage: merge_deps_versions - displayName: 'Merge Dependency Versions' - dependsOn: - - package_core - - package_core_winml - jobs: - - job: merge - displayName: 'Merge deps_versions.json' - pool: - name: onnxruntime-Win-CPU-2022 - os: windows - templateContext: - outputs: - - output: pipelineArtifact - artifactName: 'deps-versions' - targetPath: '$(Build.ArtifactStagingDirectory)/deps-versions' - steps: - - checkout: none - - task: DownloadPipelineArtifact@2 - inputs: - buildType: current - artifactName: 'deps-versions-standard' - targetPath: '$(Pipeline.Workspace)/deps-versions-standard' - - task: DownloadPipelineArtifact@2 - inputs: - buildType: current - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-winml' - - task: PowerShell@2 - displayName: 'Merge standard + WinML deps_versions.json' - inputs: - targetType: inline - script: | - $standard = Get-Content "$(Pipeline.Workspace)/deps-versions-standard/deps_versions.json" -Raw | ConvertFrom-Json - $winml = Get-Content "$(Pipeline.Workspace)/deps-versions-winml/deps_versions.json" -Raw | ConvertFrom-Json - - # Merge: take all keys from both partials - $merged = @{ - 'foundry-local-core' = @{ - nuget = $standard.'foundry-local-core'.nuget - 'nuget-winml' = $winml.'foundry-local-core'.'nuget-winml' - python = $standard.'foundry-local-core'.python - 'python-winml' = $winml.'foundry-local-core'.'python-winml' - } - onnxruntime = @{ - 'cross-plat' = $standard.onnxruntime.'cross-plat' - winml = $winml.onnxruntime.winml - } - 'onnxruntime-genai' = @{ - nuget = $standard.'onnxruntime-genai'.nuget - python = $standard.'onnxruntime-genai'.python - } - } - - $json = $merged | ConvertTo-Json -Depth 3 - Write-Host "Merged deps_versions.json:" - Write-Host $json - - $outDir = "$(Build.ArtifactStagingDirectory)/deps-versions" - New-Item -ItemType Directory -Path $outDir -Force | Out-Null - [System.IO.File]::WriteAllText("$outDir/deps_versions.json", $json, [System.Text.UTF8Encoding]::new($false)) \ No newline at end of file diff --git a/.pipelines/templates/build-python-steps.yml b/.pipelines/templates/build-python-steps.yml index 0a43ecf5..cb9950e5 100644 --- a/.pipelines/templates/build-python-steps.yml +++ b/.pipelines/templates/build-python-steps.yml @@ -119,10 +119,10 @@ steps: inputs: targetType: inline script: | - $deps = Get-Content "$(repoRoot)/sdk/deps_versions.json" -Raw | ConvertFrom-Json - $isWinML = "${{ parameters.isWinML }}" -eq "True" - $ortVer = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } - $genaiVer = $deps.'onnxruntime-genai'.python + $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 } diff --git a/.pipelines/templates/build-rust-steps.yml b/.pipelines/templates/build-rust-steps.yml index 8337f566..e6222f05 100644 --- a/.pipelines/templates/build-rust-steps.yml +++ b/.pipelines/templates/build-rust-steps.yml @@ -68,13 +68,20 @@ steps: artifactDir: ${{ parameters.depsVersionsDir }} isWinML: ${{ parameters.isWinML }} -# Copy deps_versions.json into the crate directory so cargo package -# can include it and build.rs can find it during verify. +# Copy the appropriate deps_versions JSON into the crate directory so cargo +# package can include it and build.rs can find it during verify. +# WinML builds copy deps_versions_winml.json; standard copies deps_versions.json. - task: PowerShell@2 - displayName: 'Copy deps_versions.json for crate packaging' + displayName: 'Copy deps_versions for crate packaging' inputs: targetType: inline - script: Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" + script: | + $isWinML = "${{ parameters.isWinML }}" -eq "True" + if ($isWinML) { + Copy-Item "$(repoRoot)/sdk/deps_versions_winml.json" "$(repoRoot)/sdk/rust/deps_versions_winml.json" + } else { + Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" + } # Extract FLC native binaries from the pipeline-built .nupkg so that # build.rs finds them already present and skips downloading from the feed. diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml index a7880d9e..7794924f 100644 --- a/.pipelines/templates/package-core-steps.yml +++ b/.pipelines/templates/package-core-steps.yml @@ -266,25 +266,30 @@ steps: 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-winml' = "$(flcVersion)"; 'python-winml' = $pyVer } - onnxruntime = @{ winml = [string]$pg.OnnxRuntimeFoundryVersionForWinML } - 'onnxruntime-genai' = @{ nuget = [string]$pg.OnnxRuntimeGenAIWinML; python = [string]$pg.OnnxRuntimeGenAIWinML } + 'foundry-local-core' = @{ nuget = "$(flcVersion)"; python = $pyVer } + onnxruntime = @{ version = [string]$pg.OnnxRuntimeFoundryVersionForWinML } + 'onnxruntime-genai' = @{ version = [string]$pg.OnnxRuntimeGenAIWinML } } } else { $deps = @{ 'foundry-local-core' = @{ nuget = "$(flcVersion)"; python = $pyVer } - onnxruntime = @{ 'cross-plat' = [string]$pg.OnnxRuntimeFoundryVersion } - 'onnxruntime-genai' = @{ nuget = [string]$pg.OnnxRuntimeGenAIFoundryVersion; python = [string]$pg.OnnxRuntimeGenAIFoundryVersion } + 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 "deps_versions.json (partial — $( if ($isWinML) { 'WinML' } else { 'standard' } )):" + 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/deps_versions.json", $json, [System.Text.UTF8Encoding]::new($false)) - Write-Host "Wrote deps_versions.json to $outDir" + [System.IO.File]::WriteAllText("$outDir/$fileName", $json, [System.Text.UTF8Encoding]::new($false)) + Write-Host "Wrote $fileName to $outDir" diff --git a/.pipelines/templates/test-python-steps.yml b/.pipelines/templates/test-python-steps.yml index e18b4de5..64c7f7a9 100644 --- a/.pipelines/templates/test-python-steps.yml +++ b/.pipelines/templates/test-python-steps.yml @@ -114,10 +114,10 @@ steps: inputs: targetType: inline script: | - $deps = Get-Content "$(repoRoot)/sdk/deps_versions.json" -Raw | ConvertFrom-Json - $isWinML = "${{ parameters.isWinML }}" -eq "True" - $ortVer = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } - $genaiVer = $deps.'onnxruntime-genai'.python + $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 } diff --git a/.pipelines/templates/test-rust-steps.yml b/.pipelines/templates/test-rust-steps.yml index bcc4d1da..90aaf301 100644 --- a/.pipelines/templates/test-rust-steps.yml +++ b/.pipelines/templates/test-rust-steps.yml @@ -61,12 +61,18 @@ steps: artifactDir: ${{ parameters.depsVersionsDir }} isWinML: ${{ parameters.isWinML }} -# Copy deps_versions.json into the crate directory so build.rs can find it. +# Copy the appropriate deps_versions JSON into the crate directory so build.rs can find it. - task: PowerShell@2 - displayName: 'Copy deps_versions.json for Rust build' + displayName: 'Copy deps_versions for Rust build' inputs: targetType: inline - script: Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" + script: | + $isWinML = "${{ parameters.isWinML }}" -eq "True" + if ($isWinML) { + Copy-Item "$(repoRoot)/sdk/deps_versions_winml.json" "$(repoRoot)/sdk/rust/deps_versions_winml.json" + } else { + Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" + } # Extract FLC native binaries from the pipeline-built .nupkg - task: PowerShell@2 diff --git a/.pipelines/templates/update-deps-versions-steps.yml b/.pipelines/templates/update-deps-versions-steps.yml index 0f9ccffa..9d489ab7 100644 --- a/.pipelines/templates/update-deps-versions-steps.yml +++ b/.pipelines/templates/update-deps-versions-steps.yml @@ -1,8 +1,6 @@ -# Shared template to update deps_versions.json from pipeline artifacts. -# If an artifact directory containing a pipeline-generated deps_versions.json -# is provided, it overwrites the repo copy so that SDK builds use the -# exact versions from the FLC build. Each SDK reads deps_versions.json -# directly via its own tooling (MSBuild, Node require(), serde_json, etc.). +# 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 @@ -10,40 +8,34 @@ parameters: - name: artifactDir type: string default: '' - displayName: 'Path to artifact directory containing pipeline-generated deps_versions.json' + 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.json from pipeline artifact' + displayName: 'Update deps_versions from pipeline artifact' inputs: targetType: inline script: | - $repoJson = "${{ parameters.repoRoot }}/sdk/deps_versions.json" + $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/deps_versions.json")) { - throw "Pipeline-built deps_versions.json not found in artifact directory: $artifactDir" + if ($artifactDir -eq '' -or -not (Test-Path "$artifactDir/$fileName")) { + throw "Pipeline-built $fileName not found in artifact directory: $artifactDir" } - Copy-Item "$artifactDir/deps_versions.json" $repoJson -Force - Write-Host "Updated repo deps_versions.json from pipeline artifact at $artifactDir" + Copy-Item "$artifactDir/$fileName" $repoJson -Force + Write-Host "Updated repo $fileName from pipeline artifact at $artifactDir" $deps = Get-Content $repoJson -Raw | ConvertFrom-Json - $isWinML = "${{ parameters.isWinML }}" -eq "True" # Log resolved versions for debugging - $flcNuget = if ($isWinML) { $deps.'foundry-local-core'.'nuget-winml' } else { $deps.'foundry-local-core'.nuget } - $flcPython = if ($isWinML) { $deps.'foundry-local-core'.'python-winml' } else { $deps.'foundry-local-core'.python } - $ort = if ($isWinML) { $deps.onnxruntime.winml } else { $deps.onnxruntime.'cross-plat' } - $genaiNuget = $deps.'onnxruntime-genai'.nuget - $genaiPython = $deps.'onnxruntime-genai'.python - - Write-Host "Dependency versions from deps_versions.json:" - Write-Host " FLC Core (NuGet): $flcNuget" - Write-Host " FLC Core (Python): $flcPython" - Write-Host " OnnxRuntime: $ort" - Write-Host " GenAI (NuGet): $genaiNuget" - Write-Host " GenAI (Python): $genaiPython" + 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/deps_versions.json b/sdk/deps_versions.json index a5a5ea2b..aa0b3fa3 100644 --- a/sdk/deps_versions.json +++ b/sdk/deps_versions.json @@ -1,16 +1,12 @@ { "foundry-local-core": { "nuget": "0.9.0-dev-202603310538-f6efa8d3", - "nuget-winml": "0.9.0-dev-202603310538-f6efa8d3", - "python": "0.9.0.dev20260327060216", - "python-winml": "0.9.0.dev20260331004032" + "python": "0.9.0.dev20260327060216" }, "onnxruntime": { - "cross-plat": "1.24.3", - "winml": "1.23.2.3" + "version": "1.24.3" }, "onnxruntime-genai": { - "nuget": "0.13.0", - "python": "0.13.0" + "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 ad2b59bd..29a6b76b 100644 --- a/sdk/js/script/install-standard.cjs +++ b/sdk/js/script/install-standard.cjs @@ -14,8 +14,8 @@ const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); const ARTIFACTS = [ { name: 'Microsoft.AI.Foundry.Local.Core', version: deps['foundry-local-core'].nuget, feed: ORT_NIGHTLY_FEED, nightly: useNightly }, - { name: os.platform() === 'linux' ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime['cross-plat'], feed: NUGET_FEED, nightly: false }, - { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai'].nuget, feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: os.platform() === 'linux' ? 'Microsoft.ML.OnnxRuntime.Gpu.Linux' : 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.version, feed: NUGET_FEED, nightly: false }, + { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai'].version, feed: ORT_NIGHTLY_FEED, nightly: useNightly }, ]; (async () => { diff --git a/sdk/js/script/install-winml.cjs b/sdk/js/script/install-winml.cjs index e8c46770..79e27db1 100644 --- a/sdk/js/script/install-winml.cjs +++ b/sdk/js/script/install-winml.cjs @@ -14,16 +14,18 @@ const path = require('path'); const { NUGET_FEED, ORT_NIGHTLY_FEED, runInstall } = require('./install-utils.cjs'); const useNightly = process.env.npm_config_nightly === 'true'; -const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); +// WinML uses its own deps_versions_winml.json with the same key structure +// as the standard deps_versions.json — no variant-specific keys needed. +const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions_winml.json')); // 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: deps['foundry-local-core']['nuget-winml'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, - { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.winml, feed: NUGET_FEED, nightly: false }, - { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai']['nuget'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.AI.Foundry.Local.Core.WinML', version: deps['foundry-local-core']['nuget'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, + { name: 'Microsoft.ML.OnnxRuntime.Foundry', version: deps.onnxruntime.version, feed: NUGET_FEED, nightly: false }, + { name: 'Microsoft.ML.OnnxRuntimeGenAI.Foundry', version: deps['onnxruntime-genai']['version'], feed: ORT_NIGHTLY_FEED, nightly: useNightly }, ]; (async () => { diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index de059439..71bb0b5c 100644 --- a/sdk/python/build_backend.py +++ b/sdk/python/build_backend.py @@ -57,26 +57,31 @@ # --------------------------------------------------------------------------- -def _load_deps_versions() -> dict: - """Load deps_versions.json.""" - # read with utf-8-sig encoding which handles the BOM from PS Set-Content (used in CI pipeline) - with open(_DEPS_VERSIONS, encoding="utf-8-sig") as f: +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() + deps = _load_deps_versions(winml=winml) if winml: - flc = f"foundry-local-core-winml=={deps['foundry-local-core']['python-winml']}" - ort = f"onnxruntime-core=={deps['onnxruntime']['winml']}" - genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['python']}" + flc = f"foundry-local-core-winml=={deps['foundry-local-core']['python']}" + ort = f"onnxruntime-core=={deps['onnxruntime']['version']}" + genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['version']}" else: flc = f"foundry-local-core=={deps['foundry-local-core']['python']}" - ort = f"onnxruntime-core=={deps['onnxruntime']['cross-plat']}" - genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['python']}" + ort = f"onnxruntime-core=={deps['onnxruntime']['version']}" + genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['version']}" return f"{base}\n{flc}\n{ort}\n{genai}\n" diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index ee0ef0ed..b3355649 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -7,24 +7,28 @@ 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"; -/// Versions loaded from deps_versions.json. +/// 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, - core_winml: String, ort: String, - ort_winml: 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("deps_versions.json").exists() { - manifest_path.join("deps_versions.json") + let json_path = if manifest_path.join(filename).exists() { + manifest_path.join(filename) } else { - manifest_path.join("../deps_versions.json") + manifest_path.join("..").join(filename) }; // Tell Cargo to rebuild if the versions file changes @@ -53,10 +57,8 @@ fn load_deps_versions() -> DepsVersions { let genai = &val["onnxruntime-genai"]; DepsVersions { core: s(flc, "nuget"), - core_winml: s(flc, "nuget-winml"), - ort: s(ort, "cross-plat"), - ort_winml: s(ort, "winml"), - genai: s(genai, "nuget"), + ort: s(ort, "version"), + genai: s(genai, "version"), } } @@ -101,12 +103,12 @@ fn get_packages(rid: &str) -> Vec { if winml { packages.push(NuGetPackage { name: "Microsoft.AI.Foundry.Local.Core.WinML", - version: deps.core_winml.clone(), + version: deps.core.clone(), feed_url: ORT_NIGHTLY_FEED, }); packages.push(NuGetPackage { name: "Microsoft.ML.OnnxRuntime.Foundry", - version: deps.ort_winml.clone(), + version: deps.ort.clone(), feed_url: NUGET_FEED, }); packages.push(NuGetPackage { From 9a18540d9efe6a9218e57a73b6d1cb06e1f6da8c Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 08:42:45 -0700 Subject: [PATCH 14/24] combine sdk build & test stages --- .pipelines/foundry-local-packaging.yml | 305 ++------------------ .pipelines/templates/build-cs-steps.yml | 41 +++ .pipelines/templates/build-js-steps.yml | 23 ++ .pipelines/templates/build-python-steps.yml | 23 ++ .pipelines/templates/build-rust-steps.yml | 23 ++ sdk/rust/build.rs | 6 +- 6 files changed, 136 insertions(+), 285 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index a314e181..4c8351e7 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -129,11 +129,11 @@ extends: # ── Build & Test FLC ── - stage: build_core - displayName: 'Build & Test FLC' + displayName: 'Build & Test 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-14' @@ -209,11 +209,11 @@ extends: # ── Package FLC ── - stage: package_core - displayName: 'Package FLC' + displayName: 'Package Core' dependsOn: build_core jobs: - job: package_flc - displayName: 'Package FLC' + displayName: 'Package Core' pool: name: onnxruntime-Win-CPU-2022 os: windows @@ -284,7 +284,7 @@ extends: # ── Build C# SDK ── - stage: build_cs - displayName: 'Build C# SDK' + displayName: 'Build & Test C# SDK' dependsOn: - package_core jobs: @@ -324,7 +324,7 @@ extends: # ── Build JS SDK ── - stage: build_js - displayName: 'Build JS SDK' + displayName: 'Build & Test JS SDK' dependsOn: - package_core jobs: @@ -364,7 +364,7 @@ extends: # ── Build Python SDK ── - stage: build_python - displayName: 'Build Python SDK' + displayName: 'Build & Test Python SDK' dependsOn: - package_core jobs: @@ -404,7 +404,7 @@ extends: # ── Build Rust SDK ── - stage: build_rust - displayName: 'Build Rust SDK' + displayName: 'Build & Test Rust SDK' dependsOn: - package_core jobs: @@ -442,148 +442,13 @@ extends: 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' - - input: pipelineArtifact - artifactName: 'deps-versions-standard' - targetPath: '$(Pipeline.Workspace)/deps-versions-standard' - 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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' - - # 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' - - input: pipelineArtifact - artifactName: 'deps-versions-standard' - targetPath: '$(Pipeline.Workspace)/deps-versions-standard' - 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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' - - # 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' - - input: pipelineArtifact - artifactName: 'deps-versions-standard' - targetPath: '$(Pipeline.Workspace)/deps-versions-standard' - 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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' - - # 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' - - input: pipelineArtifact - artifactName: 'deps-versions-standard' - targetPath: '$(Pipeline.Workspace)/deps-versions-standard' - 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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' - - # 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) ── - stage: build_core_winml - displayName: 'Build & Test FLC WinML' + displayName: 'Build & Test 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 @@ -604,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 @@ -624,11 +489,11 @@ extends: # ── Package FLC (WinML) ── - stage: package_core_winml - displayName: 'Package FLC WinML' + displayName: 'Package Core (WinML)' dependsOn: build_core_winml jobs: - job: package_flc_winml - displayName: 'Package FLC (WinML)' + displayName: 'Package Core (WinML)' pool: name: onnxruntime-Win-CPU-2022 os: windows @@ -685,7 +550,7 @@ extends: # ── Build C# SDK (WinML) ── - stage: build_cs_winml - displayName: 'Build C# SDK WinML' + displayName: 'Build & Test C# SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -726,7 +591,7 @@ extends: # ── Build JS SDK (WinML) ── - stage: build_js_winml - displayName: 'Build JS SDK WinML' + displayName: 'Build & Test JS SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -766,7 +631,7 @@ extends: # ── Build Python SDK (WinML) ── - stage: build_python_winml - displayName: 'Build Python SDK WinML' + displayName: 'Build & Test Python SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -807,7 +672,7 @@ extends: # ── Build Rust SDK (WinML) ── - stage: build_rust_winml - displayName: 'Build Rust SDK WinML' + displayName: 'Build & Test Rust SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -844,132 +709,4 @@ extends: isWinML: true flcNugetDir: '$(Pipeline.Workspace)/flc-nuget-winml' depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-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' - - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-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' - - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-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' - - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-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' - - input: pipelineArtifact - artifactName: 'deps-versions-winml' - targetPath: '$(Pipeline.Workspace)/deps-versions-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' - depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-winml' - - # TODO: Add Windows ARM64 (win-arm64) test job when a Windows ARM64 pool is available. - + 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 2bf02ab6..5d8f67c1 100644 --- a/.pipelines/templates/build-cs-steps.yml +++ b/.pipelines/templates/build-cs-steps.yml @@ -188,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 aea262d9..ca42fea1 100644 --- a/.pipelines/templates/build-js-steps.yml +++ b/.pipelines/templates/build-js-steps.yml @@ -213,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 cb9950e5..62520a77 100644 --- a/.pipelines/templates/build-python-steps.yml +++ b/.pipelines/templates/build-python-steps.yml @@ -164,3 +164,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 e6222f05..79fd11f9 100644 --- a/.pipelines/templates/build-rust-steps.yml +++ b/.pipelines/templates/build-rust-steps.yml @@ -224,3 +224,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/sdk/rust/build.rs b/sdk/rust/build.rs index b3355649..fab58ac0 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -22,7 +22,11 @@ fn load_deps_versions() -> DepsVersions { 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" }; + 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() { From e045e23203324fa1b0ece45c80fc313dcdc30f20 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 09:43:27 -0700 Subject: [PATCH 15/24] bug fixes --- .pipelines/templates/package-core-steps.yml | 2 +- sdk/cs/src/Microsoft.AI.Foundry.Local.csproj | 2 +- sdk/rust/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pipelines/templates/package-core-steps.yml b/.pipelines/templates/package-core-steps.yml index 7794924f..fdd54c28 100644 --- a/.pipelines/templates/package-core-steps.yml +++ b/.pipelines/templates/package-core-steps.yml @@ -273,7 +273,7 @@ steps: $deps = @{ 'foundry-local-core' = @{ nuget = "$(flcVersion)"; python = $pyVer } onnxruntime = @{ version = [string]$pg.OnnxRuntimeFoundryVersionForWinML } - 'onnxruntime-genai' = @{ version = [string]$pg.OnnxRuntimeGenAIWinML } + 'onnxruntime-genai' = @{ version = [string]$pg.OnnxRuntimeGenAIFoundryVersion } } } else { $deps = @{ diff --git a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj index 82989f58..c371690b 100644 --- a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -102,7 +102,7 @@ Can still be overridden via /p:FoundryLocalCoreVersion=X on the command line. --> <_DepsVersionsJson>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\..\deps_versions.json')) $(FoundryLocalCoreVersion) - $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget-winml"\s*:\s*"([^"]+)"').Groups[1].Value) + $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget"\s*:\s*"([^"]+)"').Groups[1].Value) $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget"\s*:\s*"([^"]+)"').Groups[1].Value) diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index eddb7907..af6a64f2 100644 --- a/sdk/rust/Cargo.toml +++ b/sdk/rust/Cargo.toml @@ -8,7 +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"] +include = ["src/**", "build.rs", "Cargo.toml", "README.md", "LICENSE", "deps_versions.json", "deps_versions_winml.json"] [features] default = [] From 5eb2b34dda9eb9d0f25f7bced42bad748a7b3d1b Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 09:51:40 -0700 Subject: [PATCH 16/24] stage naming --- .pipelines/foundry-local-packaging.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index 4c8351e7..dcca2ef7 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -127,9 +127,9 @@ extends: Write-Host "Python version: $pyVersion" Write-Host "FLC version: $flcVersion" - # ── Build & Test FLC ── + # ── Build FLC ── - stage: build_core - displayName: 'Build & Test Core' + displayName: 'Build Core' dependsOn: compute_version jobs: - job: flc_win_x64 @@ -284,7 +284,7 @@ extends: # ── Build C# SDK ── - stage: build_cs - displayName: 'Build & Test C# SDK' + displayName: 'Build C# SDK' dependsOn: - package_core jobs: @@ -324,7 +324,7 @@ extends: # ── Build JS SDK ── - stage: build_js - displayName: 'Build & Test JS SDK' + displayName: 'Build JS SDK' dependsOn: - package_core jobs: @@ -364,7 +364,7 @@ extends: # ── Build Python SDK ── - stage: build_python - displayName: 'Build & Test Python SDK' + displayName: 'Build Python SDK' dependsOn: - package_core jobs: @@ -404,7 +404,7 @@ extends: # ── Build Rust SDK ── - stage: build_rust - displayName: 'Build & Test Rust SDK' + displayName: 'Build Rust SDK' dependsOn: - package_core jobs: @@ -442,9 +442,9 @@ extends: flcNugetDir: '$(Pipeline.Workspace)/flc-nuget' depsVersionsDir: '$(Pipeline.Workspace)/deps-versions-standard' - # ── Build & Test FLC (WinML) ── + # ── Build FLC (WinML) ── - stage: build_core_winml - displayName: 'Build & Test Core (WinML)' + displayName: 'Build Core (WinML)' dependsOn: compute_version jobs: - job: flc_winml_win_x64 @@ -550,7 +550,7 @@ extends: # ── Build C# SDK (WinML) ── - stage: build_cs_winml - displayName: 'Build & Test C# SDK (WinML)' + displayName: 'Build C# SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -591,7 +591,7 @@ extends: # ── Build JS SDK (WinML) ── - stage: build_js_winml - displayName: 'Build & Test JS SDK (WinML)' + displayName: 'Build JS SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -631,7 +631,7 @@ extends: # ── Build Python SDK (WinML) ── - stage: build_python_winml - displayName: 'Build & Test Python SDK (WinML)' + displayName: 'Build Python SDK (WinML)' dependsOn: - package_core_winml jobs: @@ -672,7 +672,7 @@ extends: # ── Build Rust SDK (WinML) ── - stage: build_rust_winml - displayName: 'Build & Test Rust SDK (WinML)' + displayName: 'Build Rust SDK (WinML)' dependsOn: - package_core_winml jobs: From 1a67cffabe668de38befb9c317b1dcd2cccb4fc2 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 12:12:24 -0700 Subject: [PATCH 17/24] rust fix --- .pipelines/templates/build-rust-steps.yml | 26 +-- .pipelines/templates/test-cs-steps.yml | 125 -------------- .pipelines/templates/test-js-steps.yml | 139 ---------------- .pipelines/templates/test-python-steps.yml | 153 ----------------- .pipelines/templates/test-rust-steps.yml | 183 --------------------- sdk/rust/build.rs | 62 ++++++- 6 files changed, 56 insertions(+), 632 deletions(-) delete mode 100644 .pipelines/templates/test-cs-steps.yml delete mode 100644 .pipelines/templates/test-js-steps.yml delete mode 100644 .pipelines/templates/test-python-steps.yml delete mode 100644 .pipelines/templates/test-rust-steps.yml diff --git a/.pipelines/templates/build-rust-steps.yml b/.pipelines/templates/build-rust-steps.yml index 79fd11f9..a795fd84 100644 --- a/.pipelines/templates/build-rust-steps.yml +++ b/.pipelines/templates/build-rust-steps.yml @@ -116,7 +116,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)" } @@ -180,28 +182,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' diff --git a/.pipelines/templates/test-cs-steps.yml b/.pipelines/templates/test-cs-steps.yml deleted file mode 100644 index 72b7ab46..00000000 --- a/.pipelines/templates/test-cs-steps.yml +++ /dev/null @@ -1,125 +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' -- name: depsVersionsDir - type: string - default: '' - displayName: 'Path to deps-versions artifact directory' - -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 } - -# Load dependency versions from deps_versions.json -- template: update-deps-versions-steps.yml - parameters: - repoRoot: $(repoRoot) - artifactDir: ${{ parameters.depsVersionsDir }} - isWinML: ${{ parameters.isWinML }} - -- ${{ 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 }}" } - - $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 }} - 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 e9b8555e..00000000 --- a/.pipelines/templates/test-js-steps.yml +++ /dev/null @@ -1,139 +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' -- name: depsVersionsDir - type: string - default: '' - displayName: 'Path to deps-versions artifact directory' - -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' - -# Load dependency versions from deps_versions.json -- template: update-deps-versions-steps.yml - parameters: - repoRoot: $(repoRoot) - artifactDir: ${{ parameters.depsVersionsDir }} - isWinML: ${{ parameters.isWinML }} - -- task: Npm@1 - displayName: 'npm install (skip native downloads)' - inputs: - command: custom - workingDir: $(repoRoot)/sdk/js - customCommand: 'install --ignore-scripts' - -# Extract FLC from the pipeline-built artifact, then run the install script -# to fetch ORT/GenAI from public feeds (FLC download is skipped since it's already present). -- task: PowerShell@2 - displayName: 'Extract FLC and install native binaries' - 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 "Placed $($_.Name) from pipeline artifact" - } - } else { - Write-Warning "No native binaries found at $nativeDir for RID $rid" - } - - # Run preinstall + install script to fetch ORT/GenAI (FLC is already present) - Set-Location "$(repoRoot)/sdk/js" - node script/preinstall.cjs - node script/install-standard.cjs - -- 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-python-steps.yml b/.pipelines/templates/test-python-steps.yml deleted file mode 100644 index 64c7f7a9..00000000 --- a/.pipelines/templates/test-python-steps.yml +++ /dev/null @@ -1,153 +0,0 @@ -# Lightweight test-only steps for the Python SDK. -# Builds from source and runs tests — no artifact staging. -parameters: -- name: version - type: string -- name: isWinML - type: boolean - default: false -- name: flcWheelsDir - type: string - displayName: 'Path to directory containing the FLC wheels' -- name: depsVersionsDir - type: string - default: '' - displayName: 'Path to deps-versions artifact directory' - -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: UsePythonVersion@0 - displayName: 'Use Python 3.12' - 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 }} - -- task: PowerShell@2 - displayName: 'List downloaded FLC wheels' - condition: and(succeeded(), ne('${{ parameters.flcWheelsDir }}', '')) - inputs: - targetType: inline - script: | - Write-Host "Contents of ${{ parameters.flcWheelsDir }}:" - Get-ChildItem "${{ parameters.flcWheelsDir }}" -Recurse | ForEach-Object { Write-Host $_.FullName } - -- task: PowerShell@2 - displayName: 'Configure pip for Azure Artifacts' - inputs: - targetType: inline - script: | - pip config set global.index-url https://pkgs.dev.azure.com/aiinfra/PublicPackages/_packaging/ORT-Nightly/pypi/simple/ - pip config set global.extra-index-url https://pypi.org/simple/ - pip config set global.pre true - -- script: python -m pip install build - displayName: 'Install build tool' - -- task: PowerShell@2 - displayName: 'Set SDK version' - inputs: - targetType: inline - script: | - Set-Content -Path "$(repoRoot)/sdk/python/src/version.py" -Value '__version__ = "${{ parameters.version }}"' - -- task: PowerShell@2 - displayName: 'Pre-install pipeline-built FLC wheel' - condition: and(succeeded(), ne('${{ parameters.flcWheelsDir }}', '')) - inputs: - targetType: inline - script: | - # Determine platform wheel tag for the current machine - $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'arm64' } else { 'amd64' } - if ($IsLinux) { $platTag = "manylinux*x86_64" } - elseif ($IsMacOS) { $platTag = "macosx*$arch" } - else { $platTag = "win_$arch" } - - $filter = if ("${{ parameters.isWinML }}" -eq "True") { "foundry_local_core_winml*$platTag.whl" } else { "foundry_local_core-*$platTag.whl" } - $wheel = Get-ChildItem "${{ parameters.flcWheelsDir }}" -Recurse -Filter $filter | Select-Object -First 1 - if ($wheel) { - Write-Host "Installing pipeline-built FLC wheel: $($wheel.FullName)" - pip install $($wheel.FullName) - } else { - Write-Warning "No FLC wheel found matching $filter" - } - -- task: PowerShell@2 - displayName: 'Install ORT native packages' - inputs: - targetType: inline - script: | - $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' - -- ${{ if not(parameters.isWinML) }}: - - script: python -m build --wheel --outdir dist/ - displayName: 'Build wheel' - workingDirectory: $(repoRoot)/sdk/python - -- ${{ if parameters.isWinML }}: - - script: python -m build --wheel -C winml=true --outdir dist/ - displayName: 'Build wheel (WinML)' - workingDirectory: $(repoRoot)/sdk/python - -- task: PowerShell@2 - displayName: 'Install built wheel' - inputs: - targetType: inline - script: | - $wheel = (Get-ChildItem "$(repoRoot)/sdk/python/dist/*.whl" | Select-Object -First 1).FullName - pip install --no-deps $wheel - -- 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/test-rust-steps.yml b/.pipelines/templates/test-rust-steps.yml deleted file mode 100644 index 90aaf301..00000000 --- a/.pipelines/templates/test-rust-steps.yml +++ /dev/null @@ -1,183 +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' -- name: depsVersionsDir - type: string - default: '' - displayName: 'Path to deps-versions artifact directory' - -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 } - -# Load dependency versions from deps_versions.json -- template: update-deps-versions-steps.yml - parameters: - repoRoot: $(repoRoot) - artifactDir: ${{ parameters.depsVersionsDir }} - isWinML: ${{ parameters.isWinML }} - -# Copy the appropriate deps_versions JSON into the crate directory so build.rs can find it. -- task: PowerShell@2 - displayName: 'Copy deps_versions for Rust build' - inputs: - targetType: inline - script: | - $isWinML = "${{ parameters.isWinML }}" -eq "True" - if ($isWinML) { - Copy-Item "$(repoRoot)/sdk/deps_versions_winml.json" "$(repoRoot)/sdk/rust/deps_versions_winml.json" - } else { - Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" - } - -# 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/sdk/rust/build.rs b/sdk/rust/build.rs index fab58ac0..806f95fe 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -187,7 +187,28 @@ 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 expected_prefix = if pkg.name.contains("Foundry.Local.Core") { + "Microsoft.AI.Foundry.Local.Core" + } else if pkg.name.contains("OnnxRuntimeGenAI") { + "onnxruntime-genai" + } else if pkg.name.contains("OnnxRuntime") { + "onnxruntime" + } else { + "" + }; + if !expected_prefix.is_empty() { + let expected = format!("{expected_prefix}.{ext}"); + if out_dir.join(&expected).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(); @@ -266,15 +287,16 @@ 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" => "libMicrosoft.AI.Foundry.Local.Core.so", - "macos" => "libMicrosoft.AI.Foundry.Local.Core.dylib", - _ => return false, - }; - out_dir.join(core_lib).exists() + let ext = native_lib_extension(); + let prefix = if env::consts::OS == "windows" { "" } else { "lib" }; + 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() { @@ -294,7 +316,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()); From b7fee6266cf8019ec9a147bd10f92e9b23ccc554 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 12:16:18 -0700 Subject: [PATCH 18/24] rustic fix --- sdk/rust/build.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 806f95fe..8e42b1f9 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -204,7 +204,10 @@ fn download_and_extract(pkg: &NuGetPackage, rid: &str, out_dir: &Path) -> Result if !expected_prefix.is_empty() { let expected = format!("{expected_prefix}.{ext}"); if out_dir.join(&expected).exists() { - println!("cargo:warning={} already present, skipping download.", pkg.name); + println!( + "cargo:warning={} already present, skipping download.", + pkg.name + ); return Ok(()); } } @@ -290,7 +293,11 @@ fn download_and_extract(pkg: &NuGetPackage, rid: &str, out_dir: &Path) -> Result /// Check whether all required native libraries are already present in `out_dir`. fn libs_already_present(out_dir: &Path) -> bool { let ext = native_lib_extension(); - let prefix = if env::consts::OS == "windows" { "" } else { "lib" }; + let prefix = if env::consts::OS == "windows" { + "" + } else { + "lib" + }; let required = [ format!("Microsoft.AI.Foundry.Local.Core.{ext}"), format!("{prefix}onnxruntime.{ext}"), From cc289f635f325f5137aea51802b7648ceb265172 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 14:00:25 -0700 Subject: [PATCH 19/24] copilot feedback --- .pipelines/templates/build-python-steps.yml | 1 + sdk/cs/src/Microsoft.AI.Foundry.Local.csproj | 6 ++- sdk/js/script/install-standard.cjs | 7 +++- sdk/js/script/install-utils.cjs | 2 +- sdk/js/script/install-winml.cjs | 7 +++- sdk/js/script/pack.cjs | 4 +- sdk/python/build_backend.py | 21 ++++++---- sdk/rust/build.rs | 40 ++++++++++---------- 8 files changed, 54 insertions(+), 34 deletions(-) diff --git a/.pipelines/templates/build-python-steps.yml b/.pipelines/templates/build-python-steps.yml index 62520a77..5ada9cb6 100644 --- a/.pipelines/templates/build-python-steps.yml +++ b/.pipelines/templates/build-python-steps.yml @@ -119,6 +119,7 @@ steps: 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 diff --git a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj index c371690b..df8fc2cf 100644 --- a/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj +++ b/sdk/cs/src/Microsoft.AI.Foundry.Local.csproj @@ -98,9 +98,11 @@ - - <_DepsVersionsJson>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\..\deps_versions.json')) + <_DepsVersionsPath Condition="'$(UseWinML)' == 'true'">$(MSBuildThisFileDirectory)..\..\deps_versions_winml.json + <_DepsVersionsPath Condition="'$(UseWinML)' != 'true'">$(MSBuildThisFileDirectory)..\..\deps_versions.json + <_DepsVersionsJson>$([System.IO.File]::ReadAllText('$(_DepsVersionsPath)')) $(FoundryLocalCoreVersion) $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget"\s*:\s*"([^"]+)"').Groups[1].Value) $([System.Text.RegularExpressions.Regex]::Match('$(_DepsVersionsJson)', '"nuget"\s*:\s*"([^"]+)"').Groups[1].Value) diff --git a/sdk/js/script/install-standard.cjs b/sdk/js/script/install-standard.cjs index 29a6b76b..e1168db1 100644 --- a/sdk/js/script/install-standard.cjs +++ b/sdk/js/script/install-standard.cjs @@ -5,12 +5,17 @@ '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'); const useNightly = process.env.npm_config_nightly === 'true'; -const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions.json')); +// 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: deps['foundry-local-core'].nuget, feed: ORT_NIGHTLY_FEED, nightly: useNightly }, diff --git a/sdk/js/script/install-utils.cjs b/sdk/js/script/install-utils.cjs index af1712df..6c928440 100644 --- a/sdk/js/script/install-utils.cjs +++ b/sdk/js/script/install-utils.cjs @@ -113,7 +113,7 @@ async function installPackage(artifact, tempDir, binDir) { // Skip if this package's native binaries are already present (e.g. pre-populated // by a CI pipeline from a locally-built artifact). if (pkgName.startsWith('Microsoft.AI.Foundry.Local.Core') && - fs.existsSync(path.join(BIN_DIR, `Microsoft.AI.Foundry.Local.Core${EXT}`))) { + fs.existsSync(path.join(binDir, `Microsoft.AI.Foundry.Local.Core${EXT}`))) { console.log(` ${pkgName}: already present, skipping download.`); return; } diff --git a/sdk/js/script/install-winml.cjs b/sdk/js/script/install-winml.cjs index 79e27db1..da579272 100644 --- a/sdk/js/script/install-winml.cjs +++ b/sdk/js/script/install-winml.cjs @@ -10,13 +10,18 @@ 'use strict'; +const fs = require('fs'); const path = require('path'); const { NUGET_FEED, ORT_NIGHTLY_FEED, runInstall } = require('./install-utils.cjs'); const useNightly = process.env.npm_config_nightly === 'true'; // WinML uses its own deps_versions_winml.json with the same key structure // as the standard deps_versions.json — no variant-specific keys needed. -const deps = require(path.resolve(__dirname, '..', '..', 'deps_versions_winml.json')); +// 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}`; diff --git a/sdk/js/script/pack.cjs b/sdk/js/script/pack.cjs index 79a00828..f9fa3a24 100644 --- a/sdk/js/script/pack.cjs +++ b/sdk/js/script/pack.cjs @@ -25,12 +25,12 @@ 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; } 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']; } fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); execSync('npm pack', { cwd: path.join(__dirname, '..'), stdio: 'inherit' }); diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index 71bb0b5c..7952ba13 100644 --- a/sdk/python/build_backend.py +++ b/sdk/python/build_backend.py @@ -75,15 +75,20 @@ def _generate_requirements(*, winml: bool) -> str: deps = _load_deps_versions(winml=winml) if winml: - flc = f"foundry-local-core-winml=={deps['foundry-local-core']['python']}" - ort = f"onnxruntime-core=={deps['onnxruntime']['version']}" - genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['version']}" + 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: - flc = f"foundry-local-core=={deps['foundry-local-core']['python']}" - ort = f"onnxruntime-core=={deps['onnxruntime']['version']}" - genai = f"onnxruntime-genai-core=={deps['onnxruntime-genai']['version']}" - - return f"{base}\n{flc}\n{ort}\n{genai}\n" + 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" # --------------------------------------------------------------------------- diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index ff4d28f3..dc6c8e01 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -192,24 +192,26 @@ fn download_and_extract(pkg: &NuGetPackage, rid: &str, out_dir: &Path) -> Result // 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 expected_prefix = if pkg.name.contains("Foundry.Local.Core") { - "Microsoft.AI.Foundry.Local.Core" + 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") { - "onnxruntime-genai" + format!("{prefix}onnxruntime-genai.{ext}") } else if pkg.name.contains("OnnxRuntime") { - "onnxruntime" + format!("{prefix}onnxruntime.{ext}") } else { - "" + String::new() }; - if !expected_prefix.is_empty() { - let expected = format!("{expected_prefix}.{ext}"); - if out_dir.join(&expected).exists() { - println!( - "cargo:warning={} already present, skipping download.", - pkg.name - ); - return Ok(()); - } + 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)?; @@ -292,11 +294,11 @@ fn download_and_extract(pkg: &NuGetPackage, rid: &str, out_dir: &Path) -> Result /// 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" }; let required = [ format!("Microsoft.AI.Foundry.Local.Core.{ext}"), From 8370e3e5bc23025c15526856088303f8e31e16a3 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 16:11:58 -0700 Subject: [PATCH 20/24] js fixes --- sdk/js/script/install-utils.cjs | 28 ++++++++-------------------- sdk/js/script/pack.cjs | 23 ++++++++++++++++++++++- sdk/js/script/preinstall.cjs | 22 ++++++++++------------ 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/sdk/js/script/install-utils.cjs b/sdk/js/script/install-utils.cjs index 6c928440..dd2e8060 100644 --- a/sdk/js/script/install-utils.cjs +++ b/sdk/js/script/install-utils.cjs @@ -110,14 +110,6 @@ async function installPackage(artifact, tempDir, binDir) { const pkgName = artifact.name; const pkgVer = artifact.version; - // Skip if this package's native binaries are already present (e.g. pre-populated - // by a CI pipeline from a locally-built artifact). - if (pkgName.startsWith('Microsoft.AI.Foundry.Local.Core') && - fs.existsSync(path.join(binDir, `Microsoft.AI.Foundry.Local.Core${EXT}`))) { - console.log(` ${pkgName}: already present, skipping download.`); - return; - } - const baseAddress = await getBaseAddress(artifact.feed); const nameLower = pkgName.toLowerCase(); const verLower = pkgVer.toLowerCase(); @@ -144,14 +136,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)); } } @@ -161,14 +155,8 @@ 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; - } - console.log(`[foundry-local] Installing native libraries for ${RID}...`); fs.mkdirSync(binDir, { recursive: true }); diff --git a/sdk/js/script/pack.cjs b/sdk/js/script/pack.cjs index f9fa3a24..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) { @@ -29,12 +38,24 @@ try { 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', '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.'); From 1e4bc437085b5df603e380dfcbb731ddf0a983b8 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 16:22:26 -0700 Subject: [PATCH 21/24] even less stages --- .pipelines/foundry-local-packaging.yml | 34 ++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index dcca2ef7..f42d9415 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -207,13 +207,13 @@ extends: flavor: osx-arm64 platform: arm64 - # ── Package FLC ── - - stage: package_core - displayName: 'Package Core' - dependsOn: build_core - jobs: - job: 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 @@ -286,7 +286,7 @@ extends: - stage: build_cs displayName: 'Build C# SDK' dependsOn: - - package_core + - build_core jobs: - job: cs_sdk displayName: 'Build' @@ -326,7 +326,7 @@ extends: - stage: build_js displayName: 'Build JS SDK' dependsOn: - - package_core + - build_core jobs: - job: js_sdk displayName: 'Build' @@ -366,7 +366,7 @@ extends: - stage: build_python displayName: 'Build Python SDK' dependsOn: - - package_core + - build_core jobs: - job: python_sdk displayName: 'Build' @@ -406,7 +406,7 @@ extends: - stage: build_rust displayName: 'Build Rust SDK' dependsOn: - - package_core + - build_core jobs: - job: rust_sdk displayName: 'Build' @@ -487,13 +487,11 @@ extends: platform: arm64 isWinML: true - # ── Package FLC (WinML) ── - - stage: package_core_winml - displayName: 'Package Core (WinML)' - dependsOn: build_core_winml - jobs: - job: package_flc_winml displayName: 'Package Core (WinML)' + dependsOn: + - flc_winml_win_x64 + - flc_winml_win_arm64 pool: name: onnxruntime-Win-CPU-2022 os: windows @@ -552,7 +550,7 @@ extends: - stage: build_cs_winml displayName: 'Build C# SDK (WinML)' dependsOn: - - package_core_winml + - build_core_winml jobs: - job: cs_sdk_winml displayName: 'Build' @@ -593,7 +591,7 @@ extends: - stage: build_js_winml displayName: 'Build JS SDK (WinML)' dependsOn: - - package_core_winml + - build_core_winml jobs: - job: js_sdk_winml displayName: 'Build' @@ -633,7 +631,7 @@ extends: - stage: build_python_winml displayName: 'Build Python SDK (WinML)' dependsOn: - - package_core_winml + - build_core_winml jobs: - job: python_sdk_winml displayName: 'Build' @@ -674,7 +672,7 @@ extends: - stage: build_rust_winml displayName: 'Build Rust SDK (WinML)' dependsOn: - - package_core_winml + - build_core_winml jobs: - job: rust_sdk_winml displayName: 'Build' From c96365f7dcd75475771e0f8bdf44d9bbd3f6e05b Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Tue, 7 Apr 2026 16:29:41 -0700 Subject: [PATCH 22/24] copilot feedback --- .pipelines/foundry-local-packaging.yml | 83 ++++++++++++----------- .pipelines/templates/build-rust-steps.yml | 14 ++-- sdk/python/build_backend.py | 1 - sdk/rust/build.rs | 2 + 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.pipelines/foundry-local-packaging.yml b/.pipelines/foundry-local-packaging.yml index f42d9415..bb7274de 100644 --- a/.pipelines/foundry-local-packaging.yml +++ b/.pipelines/foundry-local-packaging.yml @@ -668,43 +668,46 @@ extends: 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: - - 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 + # 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-rust-steps.yml b/.pipelines/templates/build-rust-steps.yml index a795fd84..c0489f4f 100644 --- a/.pipelines/templates/build-rust-steps.yml +++ b/.pipelines/templates/build-rust-steps.yml @@ -68,20 +68,16 @@ steps: artifactDir: ${{ parameters.depsVersionsDir }} isWinML: ${{ parameters.isWinML }} -# Copy the appropriate deps_versions JSON into the crate directory so cargo -# package can include it and build.rs can find it during verify. -# WinML builds copy deps_versions_winml.json; standard copies deps_versions.json. +# 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: | - $isWinML = "${{ parameters.isWinML }}" -eq "True" - if ($isWinML) { - Copy-Item "$(repoRoot)/sdk/deps_versions_winml.json" "$(repoRoot)/sdk/rust/deps_versions_winml.json" - } else { - Copy-Item "$(repoRoot)/sdk/deps_versions.json" "$(repoRoot)/sdk/rust/deps_versions.json" - } + 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. diff --git a/sdk/python/build_backend.py b/sdk/python/build_backend.py index 7952ba13..57e96286 100644 --- a/sdk/python/build_backend.py +++ b/sdk/python/build_backend.py @@ -45,7 +45,6 @@ _PYPROJECT = _PROJECT_ROOT / "pyproject.toml" _REQUIREMENTS = _PROJECT_ROOT / "requirements.txt" _REQUIREMENTS_BASE = _PROJECT_ROOT / "requirements-base.txt" -_DEPS_VERSIONS = _PROJECT_ROOT.parent / "deps_versions.json" # The exact string in pyproject.toml to patch for the WinML variant. _STANDARD_NAME = 'name = "foundry-local-sdk"' diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index dc6c8e01..478d9357 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -310,6 +310,8 @@ fn libs_already_present(out_dir: &Path) -> bool { 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")); From 8cb8d33e9ad5d34b49b678d52db8fe5389cdbefe Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Wed, 8 Apr 2026 09:57:12 -0700 Subject: [PATCH 23/24] js & rust fixes --- sdk/js/script/install-utils.cjs | 16 ++++++++++++++++ sdk/rust/build.rs | 14 ++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/sdk/js/script/install-utils.cjs b/sdk/js/script/install-utils.cjs index dd2e8060..1ce44be0 100644 --- a/sdk/js/script/install-utils.cjs +++ b/sdk/js/script/install-utils.cjs @@ -110,6 +110,22 @@ async function installPackage(artifact, tempDir, binDir) { 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). + 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(); diff --git a/sdk/rust/build.rs b/sdk/rust/build.rs index 478d9357..7daf7a73 100644 --- a/sdk/rust/build.rs +++ b/sdk/rust/build.rs @@ -361,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()); From a2da0fdf3d4e64c2eb3d0f89873aca1e30ee14d7 Mon Sep 17 00:00:00 2001 From: Prathik Rao Date: Wed, 8 Apr 2026 13:32:00 -0700 Subject: [PATCH 24/24] js fix --- sdk/js/script/install-utils.cjs | 35 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/sdk/js/script/install-utils.cjs b/sdk/js/script/install-utils.cjs index 1ce44be0..aa74f4d5 100644 --- a/sdk/js/script/install-utils.cjs +++ b/sdk/js/script/install-utils.cjs @@ -106,24 +106,27 @@ 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). - 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; + // 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); @@ -172,6 +175,10 @@ async function runInstall(artifacts, options) { } const binDir = (options && options.binDir) || BIN_DIR; + // 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 }); @@ -179,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 {