Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 113 additions & 8 deletions csharp/src/Microsoft.ML.OnnxRuntime/NativeMethods.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using static Microsoft.ML.OnnxRuntime.NativeMethods;

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -847,7 +854,7 @@ static NativeMethods()
api_.CreateSyncStreamForEpDevice,
typeof(DOrtCreateSyncStreamForEpDevice));

OrtSyncStream_GetHandle =
OrtSyncStream_GetHandle =
(DOrtSyncStream_GetHandle)Marshal.GetDelegateForFunctionPointer(
api_.SyncStream_GetHandle,
typeof(DOrtSyncStream_GetHandle));
Expand All @@ -872,10 +879,107 @@ 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__
/// <summary>
/// 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.
/// </summary>
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))
{
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))
{
return handle;
}

string rid = RuntimeInformation.RuntimeIdentifier;
probePath = System.IO.Path.Combine(baseDir, "runtimes", rid, "native", mappedName);
if (NativeLibrary.TryLoad(probePath, assembly, searchPath, out handle))
{
return handle;
}
}

#if DEBUG
System.Console.WriteLine($"[DllImportResolver] Failed loading {mappedName} (RID: {RuntimeInformation.RuntimeIdentifier}, Assembly: {assemblyLocation})");
#endif
}
}

// Fall back to default resolution
return IntPtr.Zero;
}
#endif

[DllImport(NativeLib.DllName, CharSet = CharSet.Ansi)]
#if NETSTANDARD2_0
Expand Down Expand Up @@ -2644,7 +2748,7 @@ public delegate void DOrtAddKeyValuePair(IntPtr /* OrtKeyValuePairs* */ kvps,
byte[] /* const char* */ value);

/// <summary>
/// Get the value for the provided key.
/// Get the value for the provided key.
/// </summary>
/// <returns>Value. Returns IntPtr.Zero if key was not found.</returns>
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
Expand Down Expand Up @@ -2767,7 +2871,7 @@ out IntPtr /* OrtSyncStream** */ stream
// Auto Selection EP registration and selection customization

/// <summary>
/// Register an execution provider library.
/// Register an execution provider library.
/// The library must implement CreateEpFactories and ReleaseEpFactory.
/// </summary>
/// <param name="env">Environment to add the EP library to.</param>
Expand Down Expand Up @@ -2952,9 +3056,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,29 @@ private static Dictionary<string, string> 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;
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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'
Expand All @@ -246,7 +251,7 @@ stages:
versionSpec: "17"
jdkArchitectureOption: x64
jdkSourceOption: 'PreInstalled'

- task: PythonScript@0
displayName: 'Update CTest Path References'
inputs:
Expand Down
12 changes: 10 additions & 2 deletions tools/ci_build/github/azure-pipelines/nodejs/templates/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)'

Expand All @@ -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'
workingDirectory: '$(Build.BinariesDirectory)/e2e_test'
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
parameters:
StageSuffix: ''
AgentPool : 'macOS-15'
UseHostedVmImage: 'true'
PoolDemands: ''

stages:
- stage: Nodejs_Test_MacOS_${{ parameters.StageSuffix }}
dependsOn:
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
parameters:
AgentPool : 'macOS-15'
UseHostedVmImage: 'true'
IsMacOS : 'true'
ArtifactSuffix: ''
PoolDemands: ''

stages:
- stage: NuGet_Test_MacOS
dependsOn:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -48,4 +75,4 @@ stages:
OnnxRuntimeBuildDirectory: $(Build.BinariesDirectory)
DisableContribOps: $(DisableContribOps)
DisableMlOps: $(DisableMlOps)
IsReleaseBuild: $(IsReleaseBuild)
IsReleaseBuild: $(IsReleaseBuild)
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
Expand Down
Loading
Loading