diff --git a/.github/workflows/samples-integration-test.yml b/.github/workflows/samples-integration-test.yml index 4574af56f..0aedb0608 100644 --- a/.github/workflows/samples-integration-test.yml +++ b/.github/workflows/samples-integration-test.yml @@ -16,6 +16,9 @@ on: permissions: contents: read +env: + ORT_TELEMETRY_DISABLED: '1' + jobs: # ── Python Samples ────────────────────────────────────────────────── python-samples: @@ -32,7 +35,7 @@ jobs: clean: true - name: Setup Python - uses: actions/setup-python@v7 + uses: actions/setup-python@v6 with: python-version: '3.12' @@ -96,17 +99,17 @@ jobs: clean: true - name: Setup Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@v6 with: node-version: '20.x' - name: Setup Python (for node-gyp) - uses: actions/setup-python@v7 + uses: actions/setup-python@v6 with: python-version: '3.x' - name: Setup .NET SDK for NuGet authentication - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@v5 with: dotnet-version: '10.0.x' @@ -182,7 +185,7 @@ jobs: clean: true - name: Setup .NET SDK - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x diff --git a/.pipelines/v2/templates/steps-build-js.yml b/.pipelines/v2/templates/steps-build-js.yml index 657704bd9..cf18c80c4 100644 --- a/.pipelines/v2/templates/steps-build-js.yml +++ b/.pipelines/v2/templates/steps-build-js.yml @@ -156,6 +156,14 @@ steps: throw "WinML runtime DLL not found in native artifact: $winmlDll" } + $directMlDll = Join-Path "${{ parameters.nativeArtifactDir }}" 'DirectML.dll' + if (Test-Path $directMlDll) { + Copy-Item $directMlDll -Destination $dst -Force + Write-Host " staged DirectML.dll" + } else { + throw "DirectML.dll not found in native artifact: $directMlDll" + } + Get-ChildItem $dst | ForEach-Object { Write-Host " $($_.Name) $($_.Length) bytes" } displayName: 'Stage prebuild directory' diff --git a/.pipelines/v2/templates/steps-build-linux.yml b/.pipelines/v2/templates/steps-build-linux.yml index b1328cba2..20a2a08a0 100644 --- a/.pipelines/v2/templates/steps-build-linux.yml +++ b/.pipelines/v2/templates/steps-build-linux.yml @@ -75,27 +75,44 @@ steps: displayName: 'Dump vcpkg error logs' condition: failed() -# Stage the redistributable native artifact. Vcpkg uses static linkage on -# Linux (see sdk_v2/cpp/triplets/x64-linux.cmake), so libfoundry_local.so -# carries its transitive deps (azure-*, spdlog, fmt, libcurl, libssl/libcrypto, -# zlib, brotli*) inside itself — the only file we need to forward downstream -# is libfoundry_local.so. ORT/GenAI .so files are also present in bin/ but are -# supplied to consumers separately (pip on the Python side, NuGet on the C# -# side); test/example binaries are not part of the redistributable surface. +# Stage the redistributable native artifacts for standalone C++ consumers. - bash: | set -euo pipefail src='$(Build.SourcesDirectory)/sdk_v2/cpp/build/Linux/${{ parameters.buildConfig }}/bin' dst='$(Build.ArtifactStagingDirectory)/native' mkdir -p "$dst" - primary="$src/libfoundry_local.so" - if [ ! -f "$primary" ]; then - echo "ERROR: libfoundry_local.so not found at $primary" >&2 - exit 1 - fi - cp -P "$primary" "$dst/" - echo " staged libfoundry_local.so" - + required=( + "$src/libfoundry_local.so" + "$src/libonnxruntime-genai.so" + "$src/libonnxruntime.so" + ) + for f in "${required[@]}"; do + if [ ! -e "$f" ]; then + echo "ERROR: required native artifact not found: $f" >&2 + exit 1 + fi + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + done + for f in "$src"/libonnxruntime.so.*; do + if [ -e "$f" ]; then + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + fi + done + for f in "$src"/libonnxruntime-genai*.so; do + if [ -e "$f" ] && [ "$(basename "$f")" != "libonnxruntime-genai.so" ]; then + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + fi + done + for f in "$src"/libonnxruntime_providers_*.so; do + if [ -e "$f" ]; then + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + fi + done displayName: 'Stage native artifacts' # Stage debug symbols into a separate artifact so they can be published diff --git a/.pipelines/v2/templates/steps-build-macos.yml b/.pipelines/v2/templates/steps-build-macos.yml index a5da9fab9..eeb90ab53 100644 --- a/.pipelines/v2/templates/steps-build-macos.yml +++ b/.pipelines/v2/templates/steps-build-macos.yml @@ -81,26 +81,38 @@ steps: displayName: 'Dump vcpkg error logs' condition: failed() -# Stage the redistributable native artifact. macOS has no triplet overlay so -# vcpkg uses its default arm64-osx triplet, which is static — libfoundry_local.dylib -# carries its transitive deps (azure-*, spdlog, fmt, libcurl, libssl/libcrypto, -# zlib, brotli*) inside itself. ORT/GenAI .dylib files are supplied to consumers -# separately (pip on the Python side, NuGet on the C# side); test/example binaries -# are not part of the redistributable surface. +# Stage the redistributable native artifacts for standalone C++ consumers. - bash: | set -euo pipefail src='$(Build.SourcesDirectory)/sdk_v2/cpp/build/macOS/${{ parameters.buildConfig }}/bin' dst='$(Build.ArtifactStagingDirectory)/native' mkdir -p "$dst" - primary="$src/libfoundry_local.dylib" - if [ ! -f "$primary" ]; then - echo "ERROR: libfoundry_local.dylib not found at $primary" >&2 - exit 1 - fi - cp -P "$primary" "$dst/" - echo " staged libfoundry_local.dylib" - + required=( + "$src/libfoundry_local.dylib" + "$src/libonnxruntime-genai.dylib" + "$src/libonnxruntime.dylib" + ) + for f in "${required[@]}"; do + if [ ! -e "$f" ]; then + echo "ERROR: required native artifact not found: $f" >&2 + exit 1 + fi + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + done + for f in "$src"/libonnxruntime.*.dylib; do + if [ -e "$f" ]; then + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + fi + done + for f in "$src"/libonnxruntime_providers_*.dylib; do + if [ -e "$f" ]; then + cp -P "$f" "$dst/" + echo " staged $(basename "$f")" + fi + done displayName: 'Stage native artifacts' # Stage debug symbols into a separate artifact so they can be published diff --git a/.pipelines/v2/templates/steps-build-python.yml b/.pipelines/v2/templates/steps-build-python.yml index 50f86c9da..84201a682 100644 --- a/.pipelines/v2/templates/steps-build-python.yml +++ b/.pipelines/v2/templates/steps-build-python.yml @@ -106,10 +106,32 @@ steps: # deps (onnxruntime-{core,gpu} / onnxruntime-genai-{core,cuda}). Test # and example binaries are also filtered out upstream. We just copy # the curated artifact contents straight in. - $files = Get-ChildItem -Path "${{ parameters.nativeArtifactDir }}" -Recurse -File + $rid = '${{ parameters.rid }}' + if ($rid -like 'win-*') { + $allowed = @('foundry_local.dll', 'foundry_local.lib', 'Microsoft.Windows.AI.MachineLearning.dll', 'DirectML.dll') + } elseif ($rid -like 'linux-*') { + $allowed = @('libfoundry_local.so') + } elseif ($rid -like 'osx-*') { + $allowed = @('libfoundry_local.dylib') + } else { + throw "Unsupported Python wheel RID: $rid" + } + $files = foreach ($name in $allowed) { + Get-ChildItem -Path "${{ parameters.nativeArtifactDir }}" -Filter $name -File -ErrorAction SilentlyContinue + } if ($files.Count -eq 0) { throw "No native artifacts found under ${{ parameters.nativeArtifactDir }}" } + if ($rid -like 'win-*') { + $found = @{} + foreach ($f in $files) { + $found[$f.Name] = $true + } + $missing = @($allowed | Where-Object { -not $found.ContainsKey($_) }) + if ($missing.Count -gt 0) { + throw "Missing required Windows native artifact(s): $($missing -join ', ') under ${{ parameters.nativeArtifactDir }}" + } + } foreach ($f in $files) { Copy-Item $f.FullName -Destination $dest -Force } diff --git a/.pipelines/v2/templates/steps-build-windows.yml b/.pipelines/v2/templates/steps-build-windows.yml index 5f1d0f761..e72fde515 100644 --- a/.pipelines/v2/templates/steps-build-windows.yml +++ b/.pipelines/v2/templates/steps-build-windows.yml @@ -138,7 +138,13 @@ steps: (Join-Path $binDir 'foundry_local.dll'), (Join-Path $binDir 'foundry_local.pdb'), (Join-Path $linkDir 'foundry_local.lib'), - (Join-Path $binDir 'Microsoft.Windows.AI.MachineLearning.dll') + (Join-Path $binDir 'Microsoft.Windows.AI.MachineLearning.dll'), + (Join-Path $binDir 'DirectML.dll'), + (Join-Path $binDir 'onnxruntime.dll'), + (Join-Path $binDir 'onnxruntime-genai.dll') + ) + $optionalPatterns = @( + 'onnxruntime_providers_*.dll' ) foreach ($s in $sources) { @@ -149,6 +155,12 @@ steps: Copy-Item -Path $s -Destination $dst -Force Write-Host " staged $(Split-Path -Leaf $s)" } + foreach ($pattern in $optionalPatterns) { + Get-ChildItem -Path $binDir -Filter $pattern -File -ErrorAction SilentlyContinue | ForEach-Object { + Copy-Item -Path $_.FullName -Destination $dst -Force + Write-Host " staged $($_.Name)" + } + } - ${{ if eq(parameters.stageHeaders, true) }}: - task: CopyFiles@2 diff --git a/.pipelines/v2/templates/steps-pack-cpp-sdk.yml b/.pipelines/v2/templates/steps-pack-cpp-sdk.yml index a878dbf3d..984bc6a33 100644 --- a/.pipelines/v2/templates/steps-pack-cpp-sdk.yml +++ b/.pipelines/v2/templates/steps-pack-cpp-sdk.yml @@ -61,9 +61,8 @@ steps: Copy-Item -Path (Join-Path $includeSrc '*') -Destination $includeDst -Recurse -Force - # The SDK tgz should include only public wrapper headers. + # The SDK tgz should include public wrapper headers and their header-only dependencies. $pathsToPrune = @( - (Join-Path $includeDst 'gsl'), (Join-Path $includeDst '_manifest') ) foreach ($pathToPrune in $pathsToPrune) { @@ -72,13 +71,17 @@ steps: } } - foreach ($f in $Files) { - $src = Join-Path $nativeSrc $f - if (-not (Test-Path $src)) { - throw "Required native file not found: $src" + foreach ($entry in $Files) { + $optional = $entry.StartsWith('?') + $pattern = if ($optional) { $entry.Substring(1) } else { $entry } + $matches = @(Get-ChildItem -Path $nativeSrc -Filter $pattern -File -ErrorAction SilentlyContinue) + if ($matches.Count -eq 0 -and -not $optional) { + throw "Required native file not found: $(Join-Path $nativeSrc $pattern)" + } + foreach ($match in $matches) { + $dst = Join-Path $libDst $match.Name + Copy-Item -Path $match.FullName -Destination $dst -Force } - $dst = Join-Path $libDst (Split-Path -Leaf $src) - Copy-Item -Path $src -Destination $dst -Force } if ($SymbolsArtifact) { @@ -115,23 +118,40 @@ steps: New-SdkArchive -Rid 'win-x64' -NativeArtifact 'cpp-native-win-x64' -Files @( 'foundry_local.dll', 'foundry_local.pdb', - 'foundry_local.lib' + 'foundry_local.lib', + 'Microsoft.Windows.AI.MachineLearning.dll', + 'DirectML.dll', + 'onnxruntime.dll', + 'onnxruntime-genai.dll', + '?onnxruntime_providers_*.dll' ) New-SdkArchive -Rid 'win-arm64' -NativeArtifact 'cpp-native-win-arm64' -Files @( 'foundry_local.dll', 'foundry_local.pdb', - 'foundry_local.lib' + 'foundry_local.lib', + 'Microsoft.Windows.AI.MachineLearning.dll', + 'DirectML.dll', + 'onnxruntime.dll', + 'onnxruntime-genai.dll', + '?onnxruntime_providers_*.dll' ) New-SdkArchive -Rid 'linux-x64' -NativeArtifact 'cpp-native-linux-x64' -Files @( - 'libfoundry_local.so' + 'libfoundry_local.so', + 'libonnxruntime-genai.so', + '?libonnxruntime-genai*.so', + '?libonnxruntime_providers_*.so', + 'libonnxruntime.so*' ) -SymbolsArtifact 'cpp-native-symbols-linux-x64' -SymbolItems @( 'libfoundry_local.so.dbg' ) New-SdkArchive -Rid 'osx-arm64' -NativeArtifact 'cpp-native-osx-arm64' -Files @( - 'libfoundry_local.dylib' + 'libfoundry_local.dylib', + 'libonnxruntime-genai.dylib', + 'libonnxruntime*.dylib', + '?libonnxruntime_providers_*.dylib' ) -SymbolsArtifact 'cpp-native-symbols-osx-arm64' -SymbolItems @( 'libfoundry_local.dylib.dSYM' ) diff --git a/.pipelines/v2/templates/steps-pack-js.yml b/.pipelines/v2/templates/steps-pack-js.yml index 439ab41fd..9d7a4aff1 100644 --- a/.pipelines/v2/templates/steps-pack-js.yml +++ b/.pipelines/v2/templates/steps-pack-js.yml @@ -74,6 +74,7 @@ steps: $dst = '${{ parameters.outputDir }}' New-Item -ItemType Directory -Force -Path $dst | Out-Null Set-Location "$(Build.SourcesDirectory)/sdk_v2/js" + Copy-Item "$(Build.SourcesDirectory)/sdk_v2/deps_versions.json" -Destination "deps_versions.json" -Force npm pack --pack-destination $dst if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Get-ChildItem $dst | ForEach-Object { Write-Host " $($_.Name) $($_.Length) bytes" } diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index fd1c86b51..d7009c5e8 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -404,6 +404,13 @@ if(TARGET OnnxRuntime::OnnxRuntime) $ ) endif() + if(WinMLEpCatalog_FOUND AND EXISTS "${WINML_EP_CATALOG_DLL_DIR}/DirectML.dll") + add_custom_command(TARGET foundry_local POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${WINML_EP_CATALOG_DLL_DIR}/DirectML.dll" + $ + ) + endif() elseif(APPLE) # macOS: copy dylibs so consumers that only link libfoundry_local.dylib (e.g. cache_only_tests) find the correct # ORT version instead of any system-installed ORT, which would cause an Ort::InitApi() version mismatch. diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake index f11385873..5ed855cf1 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntime.cmake @@ -97,7 +97,7 @@ else() message(STATUS "Downloading ${ORT_PACKAGE_NAME} ${ORT_VERSION} from nuget.org") endif() else() - message(STATUS "Using pre-configured ORT_FETCH_URL: ${ORT_FETCH_URL}") + message(STATUS "Using pre-configured ORT_FETCH_URL") endif() # Normalize backslashes (Windows paths) and handle .nupkg extension @@ -106,11 +106,15 @@ else() set(_ORT_ZIP_PATH "${CMAKE_BINARY_DIR}/_deps/ortlib-download/ort.zip") get_filename_component(_ORT_ZIP_DIR "${_ORT_ZIP_PATH}" DIRECTORY) file(MAKE_DIRECTORY "${_ORT_ZIP_DIR}") - file(COPY_FILE "${ORT_FETCH_URL}" "${_ORT_ZIP_PATH}") + configure_file("${ORT_FETCH_URL}" "${_ORT_ZIP_PATH}" COPYONLY) set(ORT_FETCH_URL "${_ORT_ZIP_PATH}") endif() - FetchContent_Declare(ortlib URL ${ORT_FETCH_URL} DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_NAME ort.zip) + set(_ORT_FETCH_ARGS URL ${ORT_FETCH_URL} DOWNLOAD_NAME ort.zip) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") + list(APPEND _ORT_FETCH_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE) + endif() + FetchContent_Declare(ortlib ${_ORT_FETCH_ARGS}) FetchContent_MakeAvailable(ortlib) set(_ORT_HEADER_DIR "${ortlib_SOURCE_DIR}/build/native/include") @@ -149,7 +153,7 @@ else() message(STATUS "Downloading ${_ORT_GPU_LINUX_PACKAGE} ${ORT_VERSION} from nuget.org") endif() else() - message(STATUS "Using pre-configured ORT_GPU_LINUX_FETCH_URL: ${ORT_GPU_LINUX_FETCH_URL}") + message(STATUS "Using pre-configured ORT_GPU_LINUX_FETCH_URL") endif() # Normalize backslashes and handle .nupkg extension @@ -158,11 +162,15 @@ else() set(_ORT_GPU_ZIP_PATH "${CMAKE_BINARY_DIR}/_deps/ort_gpu_linux-download/ort_gpu_linux.zip") get_filename_component(_ORT_GPU_ZIP_DIR "${_ORT_GPU_ZIP_PATH}" DIRECTORY) file(MAKE_DIRECTORY "${_ORT_GPU_ZIP_DIR}") - file(COPY_FILE "${ORT_GPU_LINUX_FETCH_URL}" "${_ORT_GPU_ZIP_PATH}") + configure_file("${ORT_GPU_LINUX_FETCH_URL}" "${_ORT_GPU_ZIP_PATH}" COPYONLY) set(ORT_GPU_LINUX_FETCH_URL "${_ORT_GPU_ZIP_PATH}") endif() - FetchContent_Declare(ort_gpu_linux URL ${ORT_GPU_LINUX_FETCH_URL} DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_NAME ort_gpu_linux.zip) + set(_ORT_GPU_FETCH_ARGS URL ${ORT_GPU_LINUX_FETCH_URL} DOWNLOAD_NAME ort_gpu_linux.zip) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") + list(APPEND _ORT_GPU_FETCH_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE) + endif() + FetchContent_Declare(ort_gpu_linux ${_ORT_GPU_FETCH_ARGS}) FetchContent_MakeAvailable(ort_gpu_linux) set(_ORT_LIB_DIR "${ort_gpu_linux_SOURCE_DIR}/runtimes/${_ORT_PLATFORM}/native") diff --git a/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake b/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake index f83002d64..f8bdc4b10 100644 --- a/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake +++ b/sdk_v2/cpp/cmake/FindOnnxRuntimeGenAI.cmake @@ -160,7 +160,7 @@ else() message(STATUS "Downloading ${_GENAI_PACKAGE_NAME} ${ORT_GENAI_VERSION} from nuget.org") endif() else() - message(STATUS "Using caller-provided GENAI_FETCH_URL: ${GENAI_FETCH_URL}") + message(STATUS "Using caller-provided GENAI_FETCH_URL") endif() # Normalize to forward slashes — backslashes from Windows paths cause CMake @@ -173,15 +173,19 @@ else() set(_GENAI_ZIP_PATH "${CMAKE_BINARY_DIR}/_deps/genai-download/genai.zip") get_filename_component(_GENAI_ZIP_DIR "${_GENAI_ZIP_PATH}" DIRECTORY) file(MAKE_DIRECTORY "${_GENAI_ZIP_DIR}") - file(COPY_FILE "${GENAI_FETCH_URL}" "${_GENAI_ZIP_PATH}") + configure_file("${GENAI_FETCH_URL}" "${_GENAI_ZIP_PATH}" COPYONLY) set(GENAI_FETCH_URL "${_GENAI_ZIP_PATH}") - message(STATUS "Copied .nupkg to .zip for CMake extraction: ${GENAI_FETCH_URL}") + message(STATUS "Copied local GenAI .nupkg to .zip for CMake extraction") endif() - FetchContent_Declare(genailib + set(_GENAI_FETCH_ARGS URL ${GENAI_FETCH_URL} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - DOWNLOAD_NAME genai.zip # .nupkg is a ZIP; force CMake to recognize the format + DOWNLOAD_NAME genai.zip) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") + list(APPEND _GENAI_FETCH_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE) + endif() + FetchContent_Declare(genailib + ${_GENAI_FETCH_ARGS} ) FetchContent_MakeAvailable(genailib) diff --git a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake index dc732a4ff..4bf4eb0f7 100644 --- a/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake +++ b/sdk_v2/cpp/cmake/FindWinMLEpCatalog.cmake @@ -60,30 +60,32 @@ if(NOT _WINML_PLATFORM_UPPER MATCHES "^(AMD64|X64|X86_64|ARM64|AARCH64)$") return() endif() -include(cmake/nuget.cmake) - # WINML_EP_CATALOG_FETCH_URL can be set externally (e.g. for CI where nuget.org is blocked). set(WINML_EP_CATALOG_FETCH_URL "" CACHE STRING "Override URL or local path for the WinML EP Catalog NuGet package") -if(WINML_EP_CATALOG_FETCH_URL) - # Use FetchContent to download/extract the pre-downloaded package - include(FetchContent) - string(REPLACE "\\" "/" WINML_EP_CATALOG_FETCH_URL "${WINML_EP_CATALOG_FETCH_URL}") - if(WINML_EP_CATALOG_FETCH_URL MATCHES "\\.nupkg$" AND NOT WINML_EP_CATALOG_FETCH_URL MATCHES "^https?://") - set(_WINML_ZIP_PATH "${CMAKE_BINARY_DIR}/_deps/winml_ep_catalog-download/winml_ep_catalog.zip") - get_filename_component(_WINML_ZIP_DIR "${_WINML_ZIP_PATH}" DIRECTORY) - file(MAKE_DIRECTORY "${_WINML_ZIP_DIR}") - file(COPY_FILE "${WINML_EP_CATALOG_FETCH_URL}" "${_WINML_ZIP_PATH}") - set(WINML_EP_CATALOG_FETCH_URL "${_WINML_ZIP_PATH}") - endif() - FetchContent_Declare(winml_ep_catalog URL ${WINML_EP_CATALOG_FETCH_URL} DOWNLOAD_EXTRACT_TIMESTAMP TRUE DOWNLOAD_NAME winml_ep_catalog.zip) - FetchContent_MakeAvailable(winml_ep_catalog) - set(_WINML_EP_ROOT "${winml_ep_catalog_SOURCE_DIR}") - message(STATUS "WinML EP Catalog via FetchContent: ${_WINML_EP_ROOT}") -else() - install_nuget_package(Microsoft.Windows.AI.MachineLearning ${WINML_EP_CATALOG_VERSION} _WINML_EP_ROOT - SOURCE https://api.nuget.org/v3/index.json) +include(FetchContent) +set(_WINML_EP_CATALOG_URL "${WINML_EP_CATALOG_FETCH_URL}") +if(NOT _WINML_EP_CATALOG_URL) + set(_WINML_EP_CATALOG_URL + "https://api.nuget.org/v3-flatcontainer/microsoft.windows.ai.machinelearning/${WINML_EP_CATALOG_VERSION}/microsoft.windows.ai.machinelearning.${WINML_EP_CATALOG_VERSION}.nupkg") +endif() + +string(REPLACE "\\" "/" _WINML_EP_CATALOG_URL "${_WINML_EP_CATALOG_URL}") +if(_WINML_EP_CATALOG_URL MATCHES "\\.nupkg$" AND NOT _WINML_EP_CATALOG_URL MATCHES "^https?://") + set(_WINML_ZIP_PATH "${CMAKE_BINARY_DIR}/_deps/winml_ep_catalog-download/winml_ep_catalog.zip") + get_filename_component(_WINML_ZIP_DIR "${_WINML_ZIP_PATH}" DIRECTORY) + file(MAKE_DIRECTORY "${_WINML_ZIP_DIR}") + configure_file("${_WINML_EP_CATALOG_URL}" "${_WINML_ZIP_PATH}" COPYONLY) + set(_WINML_EP_CATALOG_URL "${_WINML_ZIP_PATH}") +endif() +set(_WINML_FETCH_ARGS URL ${_WINML_EP_CATALOG_URL} DOWNLOAD_NAME winml_ep_catalog.zip) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") + list(APPEND _WINML_FETCH_ARGS DOWNLOAD_EXTRACT_TIMESTAMP TRUE) endif() +FetchContent_Declare(winml_ep_catalog ${_WINML_FETCH_ARGS}) +FetchContent_MakeAvailable(winml_ep_catalog) +set(_WINML_EP_ROOT "${winml_ep_catalog_SOURCE_DIR}") +message(STATUS "WinML EP Catalog via FetchContent: ${_WINML_EP_ROOT}") # Load the package's first-party CMake config for target discovery and layout # resolution. The config lives at build/cmake/-config.cmake diff --git a/sdk_v2/cpp/nuget/pack.py b/sdk_v2/cpp/nuget/pack.py index 902ba288a..ec27eaf3b 100644 --- a/sdk_v2/cpp/nuget/pack.py +++ b/sdk_v2/cpp/nuget/pack.py @@ -55,11 +55,16 @@ "osx_arm64": ("osx-arm64", "libfoundry_local.dylib"), } -# Sibling files copied into runtimes//native/ when present in the upstream -# artifact. Windows builds drop Microsoft.Windows.AI.MachineLearning.dll alongside -# foundry_local.dll; other platforms don't, so presence alone drives inclusion. +# Optional sibling files copied into runtimes//native/ when present in the upstream +# artifact. Non-Windows platforms do not have these files, so presence alone drives inclusion there. OPTIONAL_SIBLINGS: tuple[str, ...] = ( +) + +# Required Windows runtime siblings. The reg-free WinML runtime depends on DirectML, so a Windows RID +# with only Microsoft.Windows.AI.MachineLearning.dll is incomplete and must fail packaging. +WINDOWS_REQUIRED_SIBLINGS: tuple[str, ...] = ( "Microsoft.Windows.AI.MachineLearning.dll", + "DirectML.dll", ) log = logging.getLogger("pack") @@ -159,6 +164,16 @@ def stage(args: argparse.Namespace, staging: Path) -> int: log.info(" %s → runtimes/%s/native/%s", lib_path, rid, lib_path.name) staged = 1 + required_siblings = WINDOWS_REQUIRED_SIBLINGS if rid.startswith("win-") else () + for sibling in required_siblings: + sibling_path = src_dir / sibling + if not sibling_path.is_file(): + log.error("Expected Windows runtime dependency %s at %s but file not found.", sibling, src_dir) + sys.exit(1) + shutil.copy2(sibling_path, native_dir) + log.info(" %s → runtimes/%s/native/%s", sibling_path, rid, sibling) + staged += 1 + for sibling in OPTIONAL_SIBLINGS: sibling_path = src_dir / sibling if sibling_path.is_file(): diff --git a/sdk_v2/js/script/copy-native.mjs b/sdk_v2/js/script/copy-native.mjs index 494d26671..34682f7dc 100644 --- a/sdk_v2/js/script/copy-native.mjs +++ b/sdk_v2/js/script/copy-native.mjs @@ -55,6 +55,7 @@ const wanted = (() => { "onnxruntime-genai.dll", "onnxruntime_providers_shared.dll", "Microsoft.Windows.AI.MachineLearning.dll", + "DirectML.dll", ]; } if (process.platform === "darwin") { diff --git a/sdk_v2/js/script/install-native.cjs b/sdk_v2/js/script/install-native.cjs index bbb372091..ae32ce0b4 100644 --- a/sdk_v2/js/script/install-native.cjs +++ b/sdk_v2/js/script/install-native.cjs @@ -30,7 +30,6 @@ const PLATFORM_MAP = { 'win32-x64': 'win-x64', 'win32-arm64': 'win-arm64', 'linux-x64': 'linux-x64', - 'linux-arm64': 'linux-arm64', 'darwin-arm64': 'osx-arm64', }; const platformKey = `${os.platform()}-${os.arch()}`; diff --git a/sdk_v2/js/script/pack-prebuilds.mjs b/sdk_v2/js/script/pack-prebuilds.mjs index 36e1d0913..5dbf558aa 100644 --- a/sdk_v2/js/script/pack-prebuilds.mjs +++ b/sdk_v2/js/script/pack-prebuilds.mjs @@ -43,18 +43,14 @@ const destDir = resolve(pkgRoot, "prebuilds", `${process.platform}-${process.arc mkdirSync(destDir, { recursive: true }); // foundry_local is required; ORT/GenAI siblings are excluded (fetched at install -// time). The WinML EP catalog DLL is an optional sibling bundled on Windows. +// time). The WinML EP catalog and DirectML DLLs are required siblings on Windows. const wanted = (() => { - if (process.platform === "win32") return ["foundry_local.dll"]; + if (process.platform === "win32") return ["foundry_local.dll", "Microsoft.Windows.AI.MachineLearning.dll", "DirectML.dll"]; if (process.platform === "darwin") return ["libfoundry_local.dylib"]; return ["libfoundry_local.so"]; })(); -// Optional native siblings — copied when present, skipped (with a warning) when -// not. The reg-free WinML 2.x runtime ships next to foundry_local.dll on Windows -// so WinML hardware EPs work out of the box without an install-time download. -const optional = - process.platform === "win32" ? ["Microsoft.Windows.AI.MachineLearning.dll"] : []; +const optional = []; let copied = 0; const available = new Set(readdirSync(sourceDir)); diff --git a/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py b/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py index 1369759c8..0658c08d7 100644 --- a/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py +++ b/sdk_v2/python/src/foundry_local_sdk/_native/lib_loader.py @@ -15,6 +15,7 @@ import pathlib import platform import sys +import sysconfig logger = logging.getLogger(__name__) @@ -29,7 +30,7 @@ def _lib_name() -> str: def _platform_subdir() -> str: if sys.platform == "win32": - return "win-x64" + return "win-arm64" if sysconfig.get_platform().lower() == "win-arm64" else "win-x64" if sys.platform == "darwin": return "osx-arm64" if platform.machine() == "arm64" else "osx-x64" return "linux-x64" @@ -248,7 +249,7 @@ def prepare_native_dependencies(foundry_local_dir: pathlib.Path) -> list: if add_dll_directory is not None: for d in {ort_path.parent, genai_path.parent, foundry_local_dir}: try: - add_dll_directory(str(d)) + handles.append(add_dll_directory(str(d))) except OSError as exc: logger.warning("os.add_dll_directory(%s) failed: %s", d, exc) @@ -278,22 +279,10 @@ def prepare_native_dependencies(foundry_local_dir: pathlib.Path) -> list: logger.warning("Failed to preload ORT (%s): %s", ort_path, exc) return handles - # macOS only: GenAI's static initializer does its own dlopen("libonnxruntime.dylib"), - # which on Darwin only matches by leafname against dyld search paths or images - # whose install_name leaf is exactly "libonnxruntime.dylib". ORT's dylib has a - # versioned install_name instead (the soversion, e.g. "@rpath/libonnxruntime.1.dylib"), - # so neither match path succeeds and GenAI aborts in dyld init before any of our code - # runs. The second name GenAI tries is "/libonnxruntime.dylib", so a symlink - # there fixes it. Linux dlopen consults the loaded-soname table by leafname and finds - # our already-RTLD_GLOBAL'd image without help. - if sys.platform == "darwin": - symlink_path = genai_path.parent / "libonnxruntime.dylib" - try: - if not symlink_path.exists(): - symlink_path.symlink_to(ort_path) - logger.info("Created macOS GenAI->ORT symlink: %s -> %s", symlink_path, ort_path) - except OSError as exc: - logger.warning("Failed to create macOS ORT symlink at %s: %s", symlink_path, exc) + # GenAI's lazy InitApi() first checks ORT_LIB_PATH before falling back to a + # platform leafname. Point it at the exact ORT dylib/so we resolved instead + # of mutating the installed GenAI package to add an unversioned alias. + os.environ["ORT_LIB_PATH"] = str(ort_path) try: handles.append(ctypes.CDLL(str(genai_path), mode=ctypes.RTLD_GLOBAL))