diff --git a/csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs b/csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs index 1ae7b5c9eb991..abe73b77f4071 100644 --- a/csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs +++ b/csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Reflection; using System.Runtime.InteropServices; using static Microsoft.ML.OnnxRuntime.NativeMethods; @@ -474,6 +475,12 @@ internal static class NativeMethods static NativeMethods() { +#if !NETSTANDARD2_0 && !__ANDROID__ && !__IOS__ + // Register a custom DllImportResolver to handle platform-specific library loading. + // Replaces default resolution specifically on Windows for case-sensitivity. + NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, DllImportResolver); +#endif + #if NETSTANDARD2_0 IntPtr ortApiBasePtr = OrtGetApiBase(); OrtApiBase ortApiBase = (OrtApiBase)Marshal.PtrToStructure(ortApiBasePtr, typeof(OrtApiBase)); @@ -847,7 +854,7 @@ static NativeMethods() api_.CreateSyncStreamForEpDevice, typeof(DOrtCreateSyncStreamForEpDevice)); - OrtSyncStream_GetHandle = + OrtSyncStream_GetHandle = (DOrtSyncStream_GetHandle)Marshal.GetDelegateForFunctionPointer( api_.SyncStream_GetHandle, typeof(DOrtSyncStream_GetHandle)); @@ -872,11 +879,127 @@ internal class NativeLib // Define the library name required for iOS internal const string DllName = "__Internal"; #else - // Note: the file name in ONNX Runtime nuget package must be onnxruntime.dll instead of onnxruntime.DLL(Windows filesystem can be case sensitive) - internal const string DllName = "onnxruntime.dll"; + // For desktop platforms (including .NET Standard 2.0), we use the simple name + // to allow .NET's automatic platform-specific resolution (lib*.so, lib*.dylib, *.dll). + // For .NET Core 3.0+, case-sensitivity on Windows is handled by DllImportResolver. + internal const string DllName = "onnxruntime"; #endif } +#if !NETSTANDARD2_0 && !__ANDROID__ && !__IOS__ + /// + /// Custom DllImportResolver to handle platform-specific library loading. + /// On Windows, it explicitly loads the library with a lowercase .dll extension to handle + /// case-sensitive filesystems. + /// + private static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == NativeLib.DllName || libraryName == OrtExtensionsNativeMethods.ExtensionsDllName) + { + string mappedName = null; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Explicitly load with .dll extension to avoid issues where the OS might try .DLL + mappedName = libraryName + ".dll"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // Explicitly load with .so extension and lib prefix + mappedName = "lib" + libraryName + ".so"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // Explicitly load with .dylib extension and lib prefix + mappedName = "lib" + libraryName + ".dylib"; + } + + if (mappedName != null) + { + // 1. Try default loading (name only) + if (NativeLibrary.TryLoad(mappedName, assembly, searchPath, out IntPtr handle)) + { + return handle; + } + + // 2. Try relative to assembly location (look into runtimes subfolders) + string assemblyLocation = null; + try { assemblyLocation = assembly.Location; } catch { } + if (!string.IsNullOrEmpty(assemblyLocation)) + { + string assemblyDir = System.IO.Path.GetDirectoryName(assemblyLocation); + string rid = RuntimeInformation.RuntimeIdentifier; + + // Probe the specific RID first, then common fallbacks for the current OS + string[] ridsToTry; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + ridsToTry = new[] { rid, "win-x64", "win-arm64" }; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + ridsToTry = new[] { rid, "linux-x64", "linux-arm64" }; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // We no longer provide osx-x64 in official package since 1.24. + // However, we keep it in the list for build-from-source users. + ridsToTry = new[] { rid, "osx-arm64", "osx-x64" }; + } + else + { + ridsToTry = new[] { rid }; + } + + foreach (var tryRid in ridsToTry) + { + string probePath = System.IO.Path.Combine(assemblyDir, "runtimes", tryRid, "native", mappedName); + if (System.IO.File.Exists(probePath) && NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle)) + { + LogLibLoad($"[DllImportResolver] Loaded {mappedName} from: {probePath}"); + return handle; + } + } + } + + // 3. Try AppContext.BaseDirectory as a fallback + string baseDir = AppContext.BaseDirectory; + if (!string.IsNullOrEmpty(baseDir)) + { + string probePath = System.IO.Path.Combine(baseDir, mappedName); + if (NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle)) + { + LogLibLoad($"[DllImportResolver] Loaded {mappedName} from: {probePath}"); + return handle; + } + + string rid = RuntimeInformation.RuntimeIdentifier; + probePath = System.IO.Path.Combine(baseDir, "runtimes", rid, "native", mappedName); + if (NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle)) + { + LogLibLoad($"[DllImportResolver] Loaded {mappedName} from: {probePath}"); + return handle; + } + } + + LogLibLoad($"[DllImportResolver] Failed loading {mappedName} (RID: {RuntimeInformation.RuntimeIdentifier}, Assembly: {assemblyLocation})"); + + } + } + + // Fall back to default resolution + return IntPtr.Zero; + } + + private static void LogLibLoad(string message) + { + System.Diagnostics.Trace.WriteLine(message); + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ORT_LOADER_VERBOSITY"))) + { + Console.WriteLine(message); + } + } +#endif + [DllImport(NativeLib.DllName, CharSet = CharSet.Ansi)] #if NETSTANDARD2_0 public static extern IntPtr OrtGetApiBase(); @@ -2644,7 +2767,7 @@ public delegate void DOrtAddKeyValuePair(IntPtr /* OrtKeyValuePairs* */ kvps, byte[] /* const char* */ value); /// - /// Get the value for the provided key. + /// Get the value for the provided key. /// /// Value. Returns IntPtr.Zero if key was not found. [UnmanagedFunctionPointer(CallingConvention.Winapi)] @@ -2767,7 +2890,7 @@ out IntPtr /* OrtSyncStream** */ stream // Auto Selection EP registration and selection customization /// - /// Register an execution provider library. + /// Register an execution provider library. /// The library must implement CreateEpFactories and ReleaseEpFactory. /// /// Environment to add the EP library to. @@ -2952,9 +3075,10 @@ internal static class OrtExtensionsNativeMethods #elif __IOS__ internal const string ExtensionsDllName = "__Internal"; #else - // For desktop platforms, explicitly specify the DLL name with extension to avoid - // issues on case-sensitive filesystems. See NativeLib.DllName for detailed explanation. - internal const string ExtensionsDllName = "ortextensions.dll"; + // For desktop platforms, use the simple name to allow .NET's + // automatic platform-specific resolution (lib*.so, lib*.dylib, *.dll). + // Case-sensitivity on Windows is handled by DllImportResolver. + internal const string ExtensionsDllName = "ortextensions"; #endif [DllImport(ExtensionsDllName, CharSet = CharSet.Ansi, diff --git a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/InferenceTest.netcore.cs b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/InferenceTest.netcore.cs index f0d1313783643..c0475bb6102c1 100644 --- a/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/InferenceTest.netcore.cs +++ b/csharp/test/Microsoft.ML.OnnxRuntime.Tests.NetCoreApp/InferenceTest.netcore.cs @@ -601,6 +601,29 @@ private static Dictionary GetSkippedModels(DirectoryInfo modelsD skipModels["VGG 16-fp32"] = "bad allocation"; } + // The following models are from onnx repo and fail on MacOS nuget test pipeline. + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var macOSSkips = new[] + { + "test_castlike_FLOAT_to_STRING_expanded", + "test_castlike_FLOAT_to_BFLOAT16_expanded", + "test_castlike_BFLOAT16_to_FLOAT", + "test_cast_FLOAT_to_STRING", + "test_castlike_FLOAT_to_BFLOAT16", + "test_castlike_STRING_to_FLOAT_expanded", + "test_castlike_STRING_to_FLOAT", + "test_cast_STRING_to_FLOAT", + "test_castlike_BFLOAT16_to_FLOAT_expanded", + "test_cast_BFLOAT16_to_FLOAT", + "test_castlike_FLOAT_to_STRING" + }; + foreach (var model in macOSSkips) + { + skipModels[model] = "Skipped on macOS due to flakes or lack of support"; + } + } + return skipModels; } @@ -934,6 +957,7 @@ public void TestPretrainedModelsWithOrtValue(string opsetDir, string modelName) [MemberData(nameof(GetSkippedModelForTest), Skip = "Skipped due to Error, please fix the error and enable the test")] private void TestPreTrainedModels(string opsetDir, string modelName, bool useOrtValueAPIs = false) { + var opsetDirInfo = new DirectoryInfo(opsetDir); var opset = opsetDirInfo.Name; string onnxModelFileName = null; diff --git a/tools/ci_build/github/azure-pipelines/c-api-noopenmp-test-pipelines.yml b/tools/ci_build/github/azure-pipelines/c-api-noopenmp-test-pipelines.yml index 7242c5fe7b6a6..8d96c1ae99e0a 100644 --- a/tools/ci_build/github/azure-pipelines/c-api-noopenmp-test-pipelines.yml +++ b/tools/ci_build/github/azure-pipelines/c-api-noopenmp-test-pipelines.yml @@ -104,9 +104,18 @@ stages: - template: nuget/templates/test_macos.yml parameters: - AgentPool: macOS-14 + AgentPool: 'AcesShared' + UseHostedVmImage: 'false' + PoolDemands: 'ImageOverride -equals ACES_VM_SharedPool_Sequoia' ArtifactSuffix: 'CPU' +- template: nodejs/templates/test_macos.yml + parameters: + AgentPool: 'AcesShared' + UseHostedVmImage: 'false' + PoolDemands: 'ImageOverride -equals ACES_VM_SharedPool_Sequoia' + StageSuffix: 'MacOS_ARM64' + - template: nodejs/templates/test_win.yml parameters: AgentPool: 'onnxruntime-Win-CPU-VS2022-Latest' @@ -117,10 +126,6 @@ stages: AgentPool: 'onnxruntime-Ubuntu2204-AMD-CPU' StageSuffix: 'Linux_CPU_x64' -- template: nodejs/templates/test_macos.yml - parameters: - StageSuffix: 'macOS_CPU_x64' - - template: nuget/templates/test_win.yml parameters: AgentPool: 'onnxruntime-Win2022-GPU-A10' @@ -225,7 +230,7 @@ stages: - checkout: self clean: true submodules: none - + - download: build artifact: 'Windows_Packaging_tensorrt_build_artifacts' displayName: 'Download Windows GPU Packages Build' @@ -246,7 +251,7 @@ stages: versionSpec: "17" jdkArchitectureOption: x64 jdkSourceOption: 'PreInstalled' - + - task: PythonScript@0 displayName: 'Update CTest Path References' inputs: diff --git a/tools/ci_build/github/azure-pipelines/nodejs/templates/test.yml b/tools/ci_build/github/azure-pipelines/nodejs/templates/test.yml index ae595bbf0c96b..cd41fc575020b 100644 --- a/tools/ci_build/github/azure-pipelines/nodejs/templates/test.yml +++ b/tools/ci_build/github/azure-pipelines/nodejs/templates/test.yml @@ -6,12 +6,20 @@ steps: - task: PowerShell@2 - displayName: 'Move Artifact Directory' + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + displayName: 'Move Artifact Directory (Windows)' inputs: targetType: 'inline' script: | Move-Item -Path "$(Pipeline.Workspace)/build/NPM_packages" -Destination "$(Build.BinariesDirectory)/nodejs-artifact" +- task: CmdLine@2 + condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) + displayName: 'Move Artifact Directory (POSIX)' + inputs: + script: | + mv "$(Pipeline.Workspace)/build/NPM_packages" "$(Build.BinariesDirectory)/nodejs-artifact" + - script: mkdir e2e_test workingDirectory: '$(Build.BinariesDirectory)' @@ -38,4 +46,4 @@ steps: npm init -y npm install $(NpmPackageFilesForTest) --onnxruntime-node-install-cuda=skip node -p "require('onnxruntime-node')" - workingDirectory: '$(Build.BinariesDirectory)/e2e_test' \ No newline at end of file + workingDirectory: '$(Build.BinariesDirectory)/e2e_test' diff --git a/tools/ci_build/github/azure-pipelines/nodejs/templates/test_macos.yml b/tools/ci_build/github/azure-pipelines/nodejs/templates/test_macos.yml index 4dd19ce2c250c..7e184492fab59 100644 --- a/tools/ci_build/github/azure-pipelines/nodejs/templates/test_macos.yml +++ b/tools/ci_build/github/azure-pipelines/nodejs/templates/test_macos.yml @@ -1,5 +1,9 @@ parameters: StageSuffix: '' + AgentPool : 'macOS-15' + UseHostedVmImage: 'true' + PoolDemands: '' + stages: - stage: Nodejs_Test_MacOS_${{ parameters.StageSuffix }} dependsOn: @@ -11,7 +15,12 @@ stages: clean: all timeoutInMinutes: 120 pool: - vmImage: 'macOS-15' + ${{ if eq(parameters.UseHostedVmImage, 'true') }}: + vmImage: ${{ parameters.AgentPool }} + ${{ else }}: + name: ${{ parameters.AgentPool }} + ${{ if ne(parameters.PoolDemands, '') }}: + demands: ${{ parameters.PoolDemands }} variables: - name: OnnxRuntimeBuildDirectory diff --git a/tools/ci_build/github/azure-pipelines/nuget/templates/test_macos.yml b/tools/ci_build/github/azure-pipelines/nuget/templates/test_macos.yml index 1d122d64b1211..5fc52e2c76468 100644 --- a/tools/ci_build/github/azure-pipelines/nuget/templates/test_macos.yml +++ b/tools/ci_build/github/azure-pipelines/nuget/templates/test_macos.yml @@ -1,6 +1,10 @@ parameters: + AgentPool : 'macOS-15' + UseHostedVmImage: 'true' IsMacOS : 'true' ArtifactSuffix: '' + PoolDemands: '' + stages: - stage: NuGet_Test_MacOS dependsOn: @@ -11,7 +15,12 @@ stages: workspace: clean: all pool: - vmImage: 'macOS-15' + ${{ if eq(parameters.UseHostedVmImage, 'true') }}: + vmImage: ${{ parameters.AgentPool }} + ${{ else }}: + name: ${{ parameters.AgentPool }} + ${{ if ne(parameters.PoolDemands, '') }}: + demands: ${{ parameters.PoolDemands }} variables: - name: OnnxRuntimeBuildDirectory @@ -27,18 +36,36 @@ stages: - script: | mv $(Pipeline.Workspace)/build/drop-signed-nuget-${{ parameters.ArtifactSuffix }} $(Build.BinariesDirectory)/nuget-artifact - mv $(Pipeline.Workspace)/build/onnxruntime-osx $(Build.BinariesDirectory)/testdata + + # Artifact is a folder containing tgz. Extract it to testdata. + mkdir -p $(Build.BinariesDirectory)/testdata + for archive in $(Pipeline.Workspace)/build/onnxruntime-osx/*.tgz; do + tar -xzf "$archive" -C $(Build.BinariesDirectory)/testdata + done + + # Ensure libcustom_op_library.dylib is where EndToEndTests expects it (testdata/testdata) + mkdir -p $(Build.BinariesDirectory)/testdata/testdata + find $(Build.BinariesDirectory)/testdata -name "libcustom_op_library.dylib" -exec cp {} $(Build.BinariesDirectory)/testdata/testdata/ \; + - template: get-nuget-package-version-as-variable.yml parameters: packageFolder: '$(Build.BinariesDirectory)/nuget-artifact' + - script: | + git submodule update --init cmake/external/onnx + cd cmake/external/onnx + git fetch origin v1.13.1 --depth=1 + git checkout v1.13.1 + cd ../../.. + displayName: 'Initialize ONNX submodule for test data (pinned to v1.13.1 since new data types like float8 is not supported in nuget)' + - script: | $(Build.SourcesDirectory)/csharp/test/Microsoft.ML.OnnxRuntime.EndToEndTests/runtest.sh \ $(Build.BinariesDirectory)/nuget-artifact \ $(NuGetPackageVersionNumber) \ true - + if [ $? -ne 0 ]; then echo "Failed to run test" exit 1 @@ -48,4 +75,5 @@ stages: OnnxRuntimeBuildDirectory: $(Build.BinariesDirectory) DisableContribOps: $(DisableContribOps) DisableMlOps: $(DisableMlOps) - IsReleaseBuild: $(IsReleaseBuild) \ No newline at end of file + IsReleaseBuild: $(IsReleaseBuild) + ORT_LOADER_VERBOSITY: 1 diff --git a/tools/ci_build/github/azure-pipelines/templates/mac-cpu-packaging-steps.yml b/tools/ci_build/github/azure-pipelines/templates/mac-cpu-packaging-steps.yml index 45f7268b9661d..795945a8581ba 100644 --- a/tools/ci_build/github/azure-pipelines/templates/mac-cpu-packaging-steps.yml +++ b/tools/ci_build/github/azure-pipelines/templates/mac-cpu-packaging-steps.yml @@ -26,6 +26,15 @@ steps: args: '-r $(Build.BinariesDirectory) -a onnxruntime-osx-${{ parameters.MacosArch }}-$(OnnxRuntimeVersion) -l libonnxruntime.$(OnnxRuntimeVersion).dylib -c Release -s $(Build.SourcesDirectory) -t $(Build.SourceVersion)' workingDirectory: '$(Build.BinariesDirectory)/Release' +- bash: | + mkdir -p $(Build.BinariesDirectory)/onnxruntime-osx-${{ parameters.MacosArch }}-$(OnnxRuntimeVersion)/testdata + cp $(Build.BinariesDirectory)/Release/libcustom_op_library.dylib $(Build.BinariesDirectory)/onnxruntime-osx-${{ parameters.MacosArch }}-$(OnnxRuntimeVersion)/testdata/libcustom_op_library.dylib + # Copy to testdata/testdata so EndToEndTests can find it when running in Debug configuration + mkdir -p $(Build.BinariesDirectory)/testdata/testdata + cp $(Build.BinariesDirectory)/Release/libcustom_op_library.dylib $(Build.BinariesDirectory)/testdata/testdata/libcustom_op_library.dylib + displayName: 'Copy custom op library' + condition: succeeded() + - task: ArchiveFiles@2 inputs: rootFolderOrFile: '$(Build.BinariesDirectory)/onnxruntime-osx-${{ parameters.MacosArch }}-$(OnnxRuntimeVersion)' diff --git a/tools/ci_build/github/linux/copy_strip_binary.sh b/tools/ci_build/github/linux/copy_strip_binary.sh index f5b4c38c85d4c..88eff3ebff86a 100755 --- a/tools/ci_build/github/linux/copy_strip_binary.sh +++ b/tools/ci_build/github/linux/copy_strip_binary.sh @@ -27,6 +27,17 @@ if [[ $LIB_NAME == *.dylib ]] then dsymutil $BINARY_DIR/$ARTIFACT_NAME/lib/$LIB_NAME -o $BINARY_DIR/$ARTIFACT_NAME/lib/$LIB_NAME.dSYM strip -S $BINARY_DIR/$ARTIFACT_NAME/lib/$LIB_NAME + + # ORT NuGet packaging expects the unversioned library (libonnxruntime.dylib) to contain the binary content, + # because the versioned library is excluded by the nuspec generation script. + # We explicitly overwrite the symlink with the real file to ensure 'nuget pack' (especially on Windows) + # doesn't pack an empty/broken symlink. + # Only applies to versioned libonnxruntime libraries (e.g. libonnxruntime.1.24.0.dylib). + if [[ "$LIB_NAME" =~ ^libonnxruntime\..*\.dylib$ && -L "$BINARY_DIR/$ARTIFACT_NAME/lib/libonnxruntime.dylib" ]]; then + rm "$BINARY_DIR/$ARTIFACT_NAME/lib/libonnxruntime.dylib" + cp "$BINARY_DIR/$ARTIFACT_NAME/lib/$LIB_NAME" "$BINARY_DIR/$ARTIFACT_NAME/lib/libonnxruntime.dylib" + fi + # copy the CoreML EP header for macOS build (libs with .dylib ext) cp $SOURCE_DIR/include/onnxruntime/core/providers/coreml/coreml_provider_factory.h $BINARY_DIR/$ARTIFACT_NAME/include else diff --git a/tools/ci_build/github/linux/docker/Dockerfile.package_ubuntu_2404_gpu b/tools/ci_build/github/linux/docker/Dockerfile.package_ubuntu_2404_gpu index 766a2c8a8b73b..0c63b7775256a 100644 --- a/tools/ci_build/github/linux/docker/Dockerfile.package_ubuntu_2404_gpu +++ b/tools/ci_build/github/linux/docker/Dockerfile.package_ubuntu_2404_gpu @@ -49,7 +49,9 @@ RUN apt-get update && \ libnvonnxparsers-dev=${TRT_VERSION} \ libnvonnxparsers10=${TRT_VERSION} \ tensorrt-dev=${TRT_VERSION} \ - libnvinfer-bin=${TRT_VERSION} && \ + libnvinfer-bin=${TRT_VERSION} \ + libnvinfer-headers-python-plugin-dev=${TRT_VERSION} \ + libnvinfer-win-builder-resource10=${TRT_VERSION} && \ rm -rf /var/lib/apt/lists/* COPY scripts /tmp/scripts