<data name="Argument_PrecisionTooLarge" xml:space="preserve">
<value>Precision cannot be larger than {0}.</value>
</data>
+ <data name="AssemblyDependencyResolver_FailedToLoadHostpolicy" xml:space="preserve">
+ <value>Cannot load hostpolicy library. AssemblyDependencyResolver is currently only supported if the runtime is hosted through hostpolicy library.</value>
+ </data>
+ <data name="AssemblyDependencyResolver_FailedToResolveDependencies" xml:space="preserve">
+ <value>Dependency resolution failed for component {0} with error code {1}. Detailed error: {2}</value>
+ </data>
<data name="Arg_EnumNotCloneable" xml:space="preserve">
<value>The supplied object does not implement ICloneable.</value>
</data>
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHandle.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SEHException.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyLoadContext.cs" />
+ <Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyDependencyResolver.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\MemoryFailPoint.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Serialization\FormatterServices.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Versioning\CompatibilitySwitch.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using Internal.IO;
+
+namespace System.Runtime.Loader
+{
+ public sealed class AssemblyDependencyResolver
+ {
+ /// <summary>
+ /// The name of the neutral culture (same value as in Variables::Init in CoreCLR)
+ /// </summary>
+ private const string NeutralCultureName = "neutral";
+
+ /// <summary>
+ /// The extension of resource assembly (same as in BindSatelliteResourceByResourceRoots in CoreCLR)
+ /// </summary>
+ private const string ResourceAssemblyExtension = ".dll";
+
+ private readonly Dictionary<string, string> _assemblyPaths;
+ private readonly string[] _nativeSearchPaths;
+ private readonly string[] _resourceSearchPaths;
+ private readonly string[] _assemblyDirectorySearchPaths;
+
+ public AssemblyDependencyResolver(string componentAssemblyPath)
+ {
+ string assemblyPathsList = null;
+ string nativeSearchPathsList = null;
+ string resourceSearchPathsList = null;
+ int returnCode = 0;
+
+ StringBuilder errorMessage = new StringBuilder();
+ try
+ {
+ // Setup error writer for this thread. This makes the hostpolicy redirect all error output
+ // to the writer specified. Have to store the previous writer to set it back once this is done.
+ corehost_error_writer_fn errorWriter = new corehost_error_writer_fn(message =>
+ {
+ errorMessage.AppendLine(message);
+ });
+
+ IntPtr errorWriterPtr = Marshal.GetFunctionPointerForDelegate(errorWriter);
+ IntPtr previousErrorWriterPtr = corehost_set_error_writer(errorWriterPtr);
+
+ try
+ {
+ // Call hostpolicy to do the actual work of finding .deps.json, parsing it and extracting
+ // information from it.
+ returnCode = corehost_resolve_component_dependencies(
+ componentAssemblyPath,
+ (assembly_paths, native_search_paths, resource_search_paths) =>
+ {
+ assemblyPathsList = assembly_paths;
+ nativeSearchPathsList = native_search_paths;
+ resourceSearchPathsList = resource_search_paths;
+ });
+ }
+ finally
+ {
+ // Reset the error write to the one used before
+ corehost_set_error_writer(previousErrorWriterPtr);
+ }
+ }
+ catch (EntryPointNotFoundException entryPointNotFoundException)
+ {
+ throw new InvalidOperationException(SR.AssemblyDependencyResolver_FailedToLoadHostpolicy, entryPointNotFoundException);
+ }
+ catch (DllNotFoundException dllNotFoundException)
+ {
+ throw new InvalidOperationException(SR.AssemblyDependencyResolver_FailedToLoadHostpolicy, dllNotFoundException);
+ }
+
+ if (returnCode != 0)
+ {
+ // Something went wrong - report a failure
+ throw new InvalidOperationException(SR.Format(
+ SR.AssemblyDependencyResolver_FailedToResolveDependencies,
+ componentAssemblyPath,
+ returnCode,
+ errorMessage.ToString()));
+ }
+
+ string[] assemblyPaths = SplitPathsList(assemblyPathsList);
+
+ // Assembly simple names are case insensitive per the runtime behavior
+ // (see SimpleNameToFileNameMapTraits for the TPA lookup hash).
+ _assemblyPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ foreach (string assemblyPath in assemblyPaths)
+ {
+ _assemblyPaths.Add(Path.GetFileNameWithoutExtension(assemblyPath), assemblyPath);
+ }
+
+ _nativeSearchPaths = SplitPathsList(nativeSearchPathsList);
+ _resourceSearchPaths = SplitPathsList(resourceSearchPathsList);
+
+ _assemblyDirectorySearchPaths = new string[1] { Path.GetDirectoryName(componentAssemblyPath) };
+ }
+
+ public string ResolveAssemblyToPath(AssemblyName assemblyName)
+ {
+ // Determine if the assembly name is for a satellite assembly or not
+ // This is the same logic as in AssemblyBinder::BindByTpaList in CoreCLR
+ // - If the culture name is non-empty and it's not 'neutral'
+ // - The culture name is the value of the AssemblyName.Culture.Name
+ // (CoreCLR gets this and stores it as the culture name in the internal assembly name)
+ // AssemblyName.CultureName is just a shortcut to AssemblyName.Culture.Name.
+ if (!string.IsNullOrEmpty(assemblyName.CultureName) &&
+ !string.Equals(assemblyName.CultureName, NeutralCultureName, StringComparison.OrdinalIgnoreCase))
+ {
+ // Load satellite assembly
+ // Search resource search paths by appending the culture name and the expected assembly file name.
+ // Copies the logic in BindSatelliteResourceByResourceRoots in CoreCLR.
+ // Note that the runtime will also probe APP_PATHS the same way, but that feature is effectively
+ // being deprecated, so we chose to not support the same behavior for components.
+ foreach (string searchPath in _resourceSearchPaths)
+ {
+ string assemblyPath = Path.Combine(
+ searchPath,
+ assemblyName.CultureName,
+ assemblyName.Name + ResourceAssemblyExtension);
+ if (File.Exists(assemblyPath))
+ {
+ return assemblyPath;
+ }
+ }
+ }
+ else
+ {
+ // Load code assembly - simply look it up in the dictionary by its simple name.
+ if (_assemblyPaths.TryGetValue(assemblyName.Name, out string assemblyPath))
+ {
+ // Only returnd the assembly if it exists on disk - this is to make the behavior of the API
+ // consistent. Resource and native resolutions will only return existing files
+ // so assembly resolution should do the same.
+ if (File.Exists(assemblyPath))
+ {
+ return assemblyPath;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public string ResolveUnmanagedDllToPath(string unmanagedDllName)
+ {
+ string[] searchPaths;
+ if (unmanagedDllName.Contains(Path.DirectorySeparatorChar))
+ {
+ // Library names with absolute or relative path can't be resolved
+ // using the component .deps.json as that defines simple names.
+ // So instead use the component directory as the lookup path.
+ searchPaths = _assemblyDirectorySearchPaths;
+ }
+ else
+ {
+ searchPaths = _nativeSearchPaths;
+ }
+
+ bool isRelativePath = !Path.IsPathFullyQualified(unmanagedDllName);
+ foreach (LibraryNameVariation libraryNameVariation in DetermineLibraryNameVariations(unmanagedDllName, isRelativePath))
+ {
+ string libraryName = libraryNameVariation.Prefix + unmanagedDllName + libraryNameVariation.Suffix;
+ foreach (string searchPath in searchPaths)
+ {
+ string libraryPath = Path.Combine(searchPath, libraryName);
+ if (File.Exists(libraryPath))
+ {
+ return libraryPath;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static string[] SplitPathsList(string pathsList)
+ {
+ if (pathsList == null)
+ {
+ return Array.Empty<string>();
+ }
+ else
+ {
+ return pathsList.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }
+
+ private struct LibraryNameVariation
+ {
+ public string Prefix;
+ public string Suffix;
+
+ public LibraryNameVariation(string prefix, string suffix)
+ {
+ Prefix = prefix;
+ Suffix = suffix;
+ }
+ }
+
+#if PLATFORM_WINDOWS
+ private const CharSet HostpolicyCharSet = CharSet.Unicode;
+ private const string LibraryNameSuffix = ".dll";
+
+ private IEnumerable<LibraryNameVariation> DetermineLibraryNameVariations(string libName, bool isRelativePath)
+ {
+ // This is a copy of the logic in DetermineLibNameVariations in dllimport.cpp in CoreCLR
+
+ yield return new LibraryNameVariation(string.Empty, string.Empty);
+
+ if (isRelativePath &&
+ !libName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) &&
+ !libName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
+ {
+ yield return new LibraryNameVariation(string.Empty, LibraryNameSuffix);
+ }
+ }
+#else
+ private const CharSet HostpolicyCharSet = CharSet.Ansi;
+
+ private const string LibraryNamePrefix = "lib";
+#if PLATFORM_OSX
+ private const string LibraryNameSuffix = ".dylib";
+#else
+ private const string LibraryNameSuffix = ".so";
+#endif
+
+ private IEnumerable<LibraryNameVariation> DetermineLibraryNameVariations(string libName, bool isRelativePath)
+ {
+ // This is a copy of the logic in DetermineLibNameVariations in dllimport.cpp in CoreCLR
+
+ if (!isRelativePath)
+ {
+ yield return new LibraryNameVariation(string.Empty, string.Empty);
+ }
+ else
+ {
+ bool containsSuffix = false;
+ int indexOfSuffix = libName.IndexOf(LibraryNameSuffix);
+ if (indexOfSuffix >= 0)
+ {
+ indexOfSuffix += LibraryNameSuffix.Length;
+ containsSuffix = indexOfSuffix == libName.Length || libName[indexOfSuffix] == '.';
+ }
+
+ bool containsDelim = libName.Contains(Path.DirectorySeparatorChar);
+
+ if (containsSuffix)
+ {
+ yield return new LibraryNameVariation(string.Empty, string.Empty);
+ if (!containsDelim)
+ {
+ yield return new LibraryNameVariation(LibraryNamePrefix, string.Empty);
+ }
+ yield return new LibraryNameVariation(string.Empty, LibraryNameSuffix);
+ if (!containsDelim)
+ {
+ yield return new LibraryNameVariation(LibraryNamePrefix, LibraryNameSuffix);
+ }
+ }
+ else
+ {
+ yield return new LibraryNameVariation(string.Empty, LibraryNameSuffix);
+ if (!containsDelim)
+ {
+ yield return new LibraryNameVariation(LibraryNamePrefix, LibraryNameSuffix);
+ }
+ yield return new LibraryNameVariation(string.Empty, string.Empty);
+ if (!containsDelim)
+ {
+ yield return new LibraryNameVariation(LibraryNamePrefix, string.Empty);
+ }
+ }
+ }
+
+ yield return new LibraryNameVariation(string.Empty, string.Empty);
+
+ if (isRelativePath &&
+ !libName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) &&
+ !libName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
+ {
+ yield return new LibraryNameVariation(string.Empty, LibraryNameSuffix);
+ }
+ }
+#endif
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = HostpolicyCharSet)]
+ internal delegate void corehost_resolve_component_dependencies_result_fn(
+ string assembly_paths,
+ string native_search_paths,
+ string resource_search_paths);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = HostpolicyCharSet)]
+ internal delegate void corehost_error_writer_fn(
+ string message);
+
+#pragma warning disable BCL0015 // Disable Pinvoke analyzer errors.
+ [DllImport("hostpolicy", CharSet = HostpolicyCharSet)]
+ private static extern int corehost_resolve_component_dependencies(
+ string component_main_assembly_path,
+ corehost_resolve_component_dependencies_result_fn result);
+
+ [DllImport("hostpolicy", CharSet = HostpolicyCharSet)]
+ private static extern IntPtr corehost_set_error_writer(IntPtr error_writer);
+#pragma warning restore
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.Reflection;
+
+namespace AssemblyDependencyResolverTests
+{
+ /// <summary>
+ /// Temporary until the actual public API gets propagated through CoreFX.
+ /// </summary>
+ public class AssemblyDependencyResolver
+ {
+ private object _implementation;
+ private Type _implementationType;
+ private MethodInfo _resolveAssemblyPathInfo;
+ private MethodInfo _resolveUnmanagedDllPathInfo;
+
+ public AssemblyDependencyResolver(string componentAssemblyPath)
+ {
+ _implementationType = typeof(object).Assembly.GetType("System.Runtime.Loader.AssemblyDependencyResolver");
+ _resolveAssemblyPathInfo = _implementationType.GetMethod("ResolveAssemblyToPath");
+ _resolveUnmanagedDllPathInfo = _implementationType.GetMethod("ResolveUnmanagedDllToPath");
+
+ try
+ {
+ _implementation = Activator.CreateInstance(_implementationType, componentAssemblyPath);
+ }
+ catch (TargetInvocationException tie)
+ {
+ throw tie.InnerException;
+ }
+ }
+
+ public string ResolveAssemblyToPath(AssemblyName assemblyName)
+ {
+ try
+ {
+ return (string)_resolveAssemblyPathInfo.Invoke(_implementation, new object[] { assemblyName });
+ }
+ catch (TargetInvocationException tie)
+ {
+ throw tie.InnerException;
+ }
+ }
+
+ public string ResolveUnmanagedDllToPath(string unmanagedDllName)
+ {
+ try
+ {
+ return (string)_resolveUnmanagedDllPathInfo.Invoke(_implementation, new object[] { unmanagedDllName });
+ }
+ catch (TargetInvocationException tie)
+ {
+ throw tie.InnerException;
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.IO;
+using System.Reflection;
+using Xunit;
+
+namespace AssemblyDependencyResolverTests
+{
+ class AssemblyDependencyResolverTests : TestBase
+ {
+ string _componentDirectory;
+ string _componentAssemblyPath;
+
+ protected override void Initialize()
+ {
+ HostPolicyMock.Initialize(TestBasePath, CoreRoot);
+ _componentDirectory = Path.Combine(TestBasePath, $"TestComponent_{Guid.NewGuid().ToString().Substring(0, 8)}");
+ Directory.CreateDirectory(_componentDirectory);
+ _componentAssemblyPath = CreateMockAssembly("TestComponent.dll");
+ }
+
+ protected override void Cleanup()
+ {
+ if (Directory.Exists(_componentDirectory))
+ {
+ Directory.Delete(_componentDirectory, recursive: true);
+ }
+ }
+
+ public void TestComponentLoadFailure()
+ {
+ const string errorMessageFirstLine = "First line: failure";
+ const string errorMessageSecondLine = "Second line: value";
+
+ using (HostPolicyMock.MockValues_corehost_set_error_writer errorWriterMock =
+ HostPolicyMock.Mock_corehost_set_error_writer())
+ {
+ using (HostPolicyMock.MockValues_corehost_resolve_componet_dependencies resolverMock =
+ HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 134,
+ "",
+ "",
+ ""))
+ {
+ // When the resolver is called, emulate error behavior
+ // which is to write to the error writer some error message.
+ resolverMock.Callback = (string componentAssemblyPath) =>
+ {
+ Assert.NotNull(errorWriterMock.LastSetErrorWriter);
+ errorWriterMock.LastSetErrorWriter(errorMessageFirstLine);
+ errorWriterMock.LastSetErrorWriter(errorMessageSecondLine);
+ };
+
+ string message = Assert.Throws<InvalidOperationException>(() =>
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+ }).Message;
+
+ Assert.Contains("134", message);
+ Assert.Contains(
+ errorMessageFirstLine + Environment.NewLine + errorMessageSecondLine,
+ message);
+
+ // After everything is done, the error writer should be reset.
+ Assert.Null(errorWriterMock.LastSetErrorWriter);
+ }
+ }
+ }
+
+ public void TestComponentLoadFailureWithPreviousErrorWriter()
+ {
+ IntPtr previousWriter = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(
+ (HostPolicyMock.ErrorWriterDelegate)((string _) => { Assert.True(false, "Should never get here"); }));
+
+ using (HostPolicyMock.MockValues_corehost_set_error_writer errorWriterMock =
+ HostPolicyMock.Mock_corehost_set_error_writer(previousWriter))
+ {
+ using (HostPolicyMock.MockValues_corehost_resolve_componet_dependencies resolverMock =
+ HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 134,
+ "",
+ "",
+ ""))
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+ });
+
+ // After everything is done, the error writer should be reset to the original value.
+ Assert.Equal(previousWriter, errorWriterMock.LastSetErrorWriterPtr);
+ }
+ }
+ }
+
+ public void TestAssembly()
+ {
+ string assemblyDependencyPath = CreateMockAssembly("AssemblyDependency.dll");
+
+ IntPtr previousWriter = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(
+ (HostPolicyMock.ErrorWriterDelegate)((string _) => { Assert.True(false, "Should never get here"); }));
+
+ using (HostPolicyMock.MockValues_corehost_set_error_writer errorWriterMock =
+ HostPolicyMock.Mock_corehost_set_error_writer(previousWriter))
+ {
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ assemblyDependencyPath,
+ "",
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ assemblyDependencyPath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("AssemblyDependency")));
+
+ // After everything is done, the error writer should be reset to the original value.
+ Assert.Equal(previousWriter, errorWriterMock.LastSetErrorWriterPtr);
+ }
+ }
+ }
+
+ public void TestAssemblyWithNoRecord()
+ {
+ // If the reqest is for assembly which is not listed in .deps.json
+ // the resolver should return null.
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ "",
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Null(resolver.ResolveAssemblyToPath(new AssemblyName("AssemblyWithNoRecord")));
+ }
+ }
+
+ public void TestAssemblyWithMissingFile()
+ {
+ // Even if the .deps.json can resolve the request, if the file is not present
+ // the resolution should still return null.
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ Path.Combine(_componentDirectory, "NonExistingAssembly.dll"),
+ "",
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Null(resolver.ResolveAssemblyToPath(new AssemblyName("NonExistingAssembly")));
+ }
+ }
+
+ public void TestSingleResource()
+ {
+ string enResourcePath = CreateMockAssembly($"en{Path.DirectorySeparatorChar}TestComponent.resources.dll");
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ "",
+ _componentDirectory))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ enResourcePath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("TestComponent.resources, Culture=en")));
+ }
+ }
+
+ public void TestMutipleResourcesWithSameBasePath()
+ {
+ string enResourcePath = CreateMockAssembly($"en{Path.DirectorySeparatorChar}TestComponent.resources.dll");
+ string csResourcePath = CreateMockAssembly($"cs{Path.DirectorySeparatorChar}TestComponent.resources.dll");
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ "",
+ _componentDirectory))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ enResourcePath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("TestComponent.resources, Culture=en")));
+ Assert.Equal(
+ csResourcePath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("TestComponent.resources, Culture=cs")));
+ }
+ }
+
+ public void TestMutipleResourcesWithDifferentBasePath()
+ {
+ string enResourcePath = CreateMockAssembly($"en{Path.DirectorySeparatorChar}TestComponent.resources.dll");
+ string frResourcePath = CreateMockAssembly($"SubComponent{Path.DirectorySeparatorChar}fr{Path.DirectorySeparatorChar}TestComponent.resources.dll");
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ "",
+ $"{_componentDirectory}{Path.PathSeparator}{Path.GetDirectoryName(Path.GetDirectoryName(frResourcePath))}"))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ enResourcePath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("TestComponent.resources, Culture=en")));
+ Assert.Equal(
+ frResourcePath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("TestComponent.resources, Culture=fr")));
+ }
+ }
+
+ public void TestAssemblyWithNeutralCulture()
+ {
+ string neutralAssemblyPath = CreateMockAssembly("NeutralAssembly.dll");
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ neutralAssemblyPath,
+ "",
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ neutralAssemblyPath,
+ resolver.ResolveAssemblyToPath(new AssemblyName("NeutralAssembly, Culture=neutral")));
+ }
+ }
+
+ public void TestSingleNativeDependency()
+ {
+ string nativeLibraryPath = CreateMockStandardNativeLibrary("native", "Single");
+
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ Path.GetDirectoryName(nativeLibraryPath),
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ nativeLibraryPath,
+ resolver.ResolveUnmanagedDllToPath("Single"));
+ }
+ }
+
+ public void TestMultipleNativeDependencies()
+ {
+ string oneNativeLibraryPath = CreateMockStandardNativeLibrary($"native{Path.DirectorySeparatorChar}one", "One");
+ string twoNativeLibraryPath = CreateMockStandardNativeLibrary($"native{Path.DirectorySeparatorChar}two", "Two");
+
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ $"{Path.GetDirectoryName(oneNativeLibraryPath)}{Path.PathSeparator}{Path.GetDirectoryName(twoNativeLibraryPath)}",
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ Assert.Equal(
+ oneNativeLibraryPath,
+ resolver.ResolveUnmanagedDllToPath("One"));
+ Assert.Equal(
+ twoNativeLibraryPath,
+ resolver.ResolveUnmanagedDllToPath("Two"));
+ }
+ }
+
+ private string CreateMockAssembly(string relativePath)
+ {
+ string fullPath = Path.Combine(_componentDirectory, relativePath);
+ if (!File.Exists(fullPath))
+ {
+ string directory = Path.GetDirectoryName(fullPath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ File.WriteAllText(fullPath, "Mock assembly");
+ }
+
+ return fullPath;
+ }
+
+ private string CreateMockStandardNativeLibrary(string relativePath, string simpleName)
+ {
+ return CreateMockAssembly(
+ relativePath + Path.DirectorySeparatorChar + XPlatformUtils.GetStandardNativeLibraryFileName(simpleName));
+ }
+
+ public static int Main()
+ {
+ return TestBase.RunTests(
+ // It's important that the invalid hosting test runs first as it relies on the ability
+ // to delete (if it's there) the hostpolicy.dll. All other tests will end up loading the dll
+ // and thus locking it.
+ typeof(InvalidHostingTest),
+ typeof(AssemblyDependencyResolverTests),
+ typeof(NativeDependencyTests));
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <CLRTestKind>BuildAndRun</CLRTestKind>
+ <CLRTestPriority>1</CLRTestPriority>
+ <ProjectGuid>{ABB86728-A3E0-4489-BD97-A0BAB00B322F}</ProjectGuid>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ </PropertyGroup>
+ <PropertyGroup>
+ <DefineConstants Condition="$(OSGroup) == 'Windows_NT'">WINDOWS</DefineConstants>
+ <DefineConstants Condition="$(OSGroup) == 'OSX'">OSX</DefineConstants>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="AssemblyDependencyResolver.cs" />
+ <Compile Include="AssemblyDependencyResolverTests.cs" />
+ <Compile Include="HostPolicyMock.cs" />
+ <Compile Include="InvalidHostingTest.cs" />
+ <Compile Include="NativeDependencyTests.cs" />
+ <Compile Include="TestBase.cs" />
+ <Compile Include="XPlatformUtils.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="CMakeLists.txt" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>
--- /dev/null
+cmake_minimum_required(VERSION 2.6)
+project (hostpolicy)
+
+set(SOURCES HostpolicyMock.cpp )
+add_library(hostpolicy SHARED ${SOURCES})
+
+install(TARGETS hostpolicy DESTINATION bin)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace AssemblyDependencyResolverTests
+{
+ class HostPolicyMock
+ {
+#if WINDOWS
+ private const CharSet HostpolicyCharSet = CharSet.Unicode;
+#else
+ private const CharSet HostpolicyCharSet = CharSet.Ansi;
+#endif
+
+ [DllImport("hostpolicy", CharSet = HostpolicyCharSet)]
+ private static extern int Set_corehost_resolve_component_dependencies_Values(
+ int returnValue,
+ string assemblyPaths,
+ string nativeSearchPaths,
+ string resourceSearchPaths);
+
+ [DllImport("hostpolicy", CharSet = HostpolicyCharSet)]
+ private static extern void Set_corehost_set_error_writer_returnValue(IntPtr error_writer);
+
+ [DllImport("hostpolicy", CharSet = HostpolicyCharSet)]
+ private static extern IntPtr Get_corehost_set_error_writer_lastSet_error_writer();
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = HostpolicyCharSet)]
+ internal delegate void Callback_corehost_resolve_component_dependencies(
+ string component_main_assembly_path);
+
+ [DllImport("hostpolicy", CharSet = HostpolicyCharSet)]
+ private static extern void Set_corehost_resolve_component_dependencies_Callback(
+ IntPtr callback);
+
+ private static Type _assemblyDependencyResolverType;
+ private static Type _corehost_error_writer_fnType;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = HostpolicyCharSet)]
+ public delegate void ErrorWriterDelegate(string message);
+
+ public static string DeleteExistingHostpolicy(string coreRoot)
+ {
+ string hostPolicyFileName = XPlatformUtils.GetStandardNativeLibraryFileName("hostpolicy");
+ string destinationPath = Path.Combine(coreRoot, hostPolicyFileName);
+ if (File.Exists(destinationPath))
+ {
+ File.Delete(destinationPath);
+ }
+
+ return destinationPath;
+ }
+
+ public static void Initialize(string testBasePath, string coreRoot)
+ {
+ string hostPolicyFileName = XPlatformUtils.GetStandardNativeLibraryFileName("hostpolicy");
+ string destinationPath = DeleteExistingHostpolicy(coreRoot);
+
+ File.Copy(
+ Path.Combine(testBasePath, hostPolicyFileName),
+ destinationPath);
+
+ _assemblyDependencyResolverType = typeof(object).Assembly.GetType("System.Runtime.Loader.AssemblyDependencyResolver");
+
+ // This is needed for marshalling of function pointers to work - requires private access to the CDR unfortunately
+ // Delegate marshalling doesn't support casting delegates to anything but the original type
+ // so we need to use the original type.
+ _corehost_error_writer_fnType = _assemblyDependencyResolverType.GetNestedType("corehost_error_writer_fn", System.Reflection.BindingFlags.NonPublic);
+ }
+
+ public static MockValues_corehost_resolve_componet_dependencies Mock_corehost_resolve_componet_dependencies(
+ int returnValue,
+ string assemblyPaths,
+ string nativeSearchPaths,
+ string resourceSearchPaths)
+ {
+ Set_corehost_resolve_component_dependencies_Values(
+ returnValue,
+ assemblyPaths,
+ nativeSearchPaths,
+ resourceSearchPaths);
+
+ return new MockValues_corehost_resolve_componet_dependencies();
+ }
+
+ internal class MockValues_corehost_resolve_componet_dependencies : IDisposable
+ {
+ public Action<string> Callback
+ {
+ set
+ {
+ var callback = new Callback_corehost_resolve_component_dependencies(value);
+ if (callback != null)
+ {
+ Set_corehost_resolve_component_dependencies_Callback(
+ Marshal.GetFunctionPointerForDelegate(callback));
+ }
+ else
+ {
+ Set_corehost_resolve_component_dependencies_Callback(IntPtr.Zero);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Set_corehost_resolve_component_dependencies_Values(
+ -1,
+ string.Empty,
+ string.Empty,
+ string.Empty);
+ Set_corehost_resolve_component_dependencies_Callback(IntPtr.Zero);
+ }
+ }
+
+ public static MockValues_corehost_set_error_writer Mock_corehost_set_error_writer()
+ {
+ return Mock_corehost_set_error_writer(IntPtr.Zero);
+ }
+
+ public static MockValues_corehost_set_error_writer Mock_corehost_set_error_writer(IntPtr existingErrorWriter)
+ {
+ Set_corehost_set_error_writer_returnValue(existingErrorWriter);
+
+ return new MockValues_corehost_set_error_writer();
+ }
+
+ internal class MockValues_corehost_set_error_writer : IDisposable
+ {
+ public IntPtr LastSetErrorWriterPtr
+ {
+ get
+ {
+ return Get_corehost_set_error_writer_lastSet_error_writer();
+ }
+ }
+
+ public Action<string> LastSetErrorWriter
+ {
+ get
+ {
+ IntPtr errorWriterPtr = LastSetErrorWriterPtr;
+ if (errorWriterPtr == IntPtr.Zero)
+ {
+ return null;
+ }
+ else
+ {
+ Delegate d = Marshal.GetDelegateForFunctionPointer(errorWriterPtr, _corehost_error_writer_fnType);
+ return (string message) => { d.DynamicInvoke(message); };
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Set_corehost_set_error_writer_returnValue(IntPtr.Zero);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Mock implementation of the hostpolicy.cpp exported methods.
+// Used for testing CoreCLR/Corlib functionality which calls into hostpolicy.
+
+#include <string>
+
+// dllexport
+#if defined _WIN32
+
+#define SHARED_API extern "C" __declspec(dllexport)
+typedef wchar_t char_t;
+typedef std::wstring string_t;
+
+#else //!_Win32
+
+#if __GNUC__ >= 4
+#define SHARED_API extern "C" __attribute__ ((visibility ("default")))
+#else
+#define SHARED_API extern "C"
+#endif
+
+typedef char char_t;
+typedef std::string string_t;
+
+#endif //_WIN32
+
+int g_corehost_resolve_component_dependencies_returnValue = -1;
+string_t g_corehost_resolve_component_dependencies_assemblyPaths;
+string_t g_corehost_resolve_component_dependencies_nativeSearchPaths;
+string_t g_corehost_resolve_component_dependencies_resourceSearchPaths;
+
+typedef void(*Callback_corehost_resolve_component_dependencies)(const char_t *component_main_assembly_path);
+Callback_corehost_resolve_component_dependencies g_corehost_resolve_component_dependencies_Callback;
+
+typedef void(*corehost_resolve_component_dependencies_result_fn)(
+ const char_t* assembly_paths,
+ const char_t* native_search_paths,
+ const char_t* resource_search_paths);
+
+SHARED_API int corehost_resolve_component_dependencies(
+ const char_t *component_main_assembly_path,
+ corehost_resolve_component_dependencies_result_fn result)
+{
+ if (g_corehost_resolve_component_dependencies_Callback != NULL)
+ {
+ g_corehost_resolve_component_dependencies_Callback(component_main_assembly_path);
+ }
+
+ if (g_corehost_resolve_component_dependencies_returnValue == 0)
+ {
+ result(
+ g_corehost_resolve_component_dependencies_assemblyPaths.data(),
+ g_corehost_resolve_component_dependencies_nativeSearchPaths.data(),
+ g_corehost_resolve_component_dependencies_resourceSearchPaths.data());
+ }
+
+ return g_corehost_resolve_component_dependencies_returnValue;
+}
+
+SHARED_API void Set_corehost_resolve_component_dependencies_Values(
+ int returnValue,
+ const char_t *assemblyPaths,
+ const char_t *nativeSearchPaths,
+ const char_t *resourceSearchPaths)
+{
+ g_corehost_resolve_component_dependencies_returnValue = returnValue;
+ g_corehost_resolve_component_dependencies_assemblyPaths.assign(assemblyPaths);
+ g_corehost_resolve_component_dependencies_nativeSearchPaths.assign(nativeSearchPaths);
+ g_corehost_resolve_component_dependencies_resourceSearchPaths.assign(resourceSearchPaths);
+}
+
+SHARED_API void Set_corehost_resolve_component_dependencies_Callback(
+ Callback_corehost_resolve_component_dependencies callback)
+{
+ g_corehost_resolve_component_dependencies_Callback = callback;
+}
+
+
+typedef void(*corehost_error_writer_fn)(const char_t* message);
+corehost_error_writer_fn g_corehost_set_error_writer_lastSet_error_writer;
+corehost_error_writer_fn g_corehost_set_error_writer_returnValue;
+
+SHARED_API corehost_error_writer_fn corehost_set_error_writer(corehost_error_writer_fn error_writer)
+{
+ g_corehost_set_error_writer_lastSet_error_writer = error_writer;
+ return g_corehost_set_error_writer_returnValue;
+}
+
+SHARED_API void Set_corehost_set_error_writer_returnValue(corehost_error_writer_fn error_writer)
+{
+ g_corehost_set_error_writer_returnValue = error_writer;
+}
+
+SHARED_API corehost_error_writer_fn Get_corehost_set_error_writer_lastSet_error_writer()
+{
+ return g_corehost_set_error_writer_lastSet_error_writer;
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.IO;
+using Xunit;
+
+namespace AssemblyDependencyResolverTests
+{
+ class InvalidHostingTest : TestBase
+ {
+ private string _componentDirectory;
+ private string _componentAssemblyPath;
+ private string _officialHostPolicyPath;
+ private string _localHostPolicyPath;
+ private string _renamedHostPolicyPath;
+
+ protected override void Initialize()
+ {
+ // Make sure there's no hostpolicy available
+ _officialHostPolicyPath = HostPolicyMock.DeleteExistingHostpolicy(CoreRoot);
+ string hostPolicyFileName = XPlatformUtils.GetStandardNativeLibraryFileName("hostpolicy");
+ _localHostPolicyPath = Path.Combine(TestBasePath, hostPolicyFileName);
+ _renamedHostPolicyPath = Path.Combine(TestBasePath, hostPolicyFileName + "_renamed");
+ if (File.Exists(_renamedHostPolicyPath))
+ {
+ File.Delete(_renamedHostPolicyPath);
+ }
+ File.Move(_localHostPolicyPath, _renamedHostPolicyPath);
+
+ _componentDirectory = Path.Combine(TestBasePath, $"InvalidHostingComponent_{Guid.NewGuid().ToString().Substring(0, 8)}");
+ Directory.CreateDirectory(_componentDirectory);
+ _componentAssemblyPath = Path.Combine(_componentDirectory, "InvalidHostingComponent.dll");
+ File.WriteAllText(_componentAssemblyPath, "Mock assembly");
+ }
+
+ protected override void Cleanup()
+ {
+ if (Directory.Exists(_componentDirectory))
+ {
+ Directory.Delete(_componentDirectory, recursive: true);
+ }
+
+ if (File.Exists(_renamedHostPolicyPath))
+ {
+ File.Move(_renamedHostPolicyPath, _localHostPolicyPath);
+ }
+ }
+
+ public void TestMissingHostPolicy()
+ {
+ object innerException = Assert.Throws<InvalidOperationException>(() =>
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+ }).InnerException;
+
+ Assert.IsType<DllNotFoundException>(innerException);
+ }
+
+ // Note: No good way to test the missing entry point case where hostpolicy.dll
+ // exists, but it doesn't have the right entry points.
+ // Loading a "wrong" hostpolicy.dll into the process is non-revertable operation
+ // so we would not be able to run other tests along side this one.
+ // Having a standalone .exe just for that one test is not worth it.
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace AssemblyDependencyResolverTests
+{
+ class NativeDependencyTests : TestBase
+ {
+ string _componentDirectory;
+ string _componentAssemblyPath;
+
+ protected override void Initialize()
+ {
+ HostPolicyMock.Initialize(TestBasePath, CoreRoot);
+ _componentDirectory = Path.Combine(TestBasePath, $"TestComponent_{Guid.NewGuid().ToString().Substring(0, 8)}");
+
+ Directory.CreateDirectory(_componentDirectory);
+ _componentAssemblyPath = CreateMockFile("TestComponent.dll");
+ }
+
+ protected override void Cleanup()
+ {
+ if (Directory.Exists(_componentDirectory))
+ {
+ Directory.Delete(_componentDirectory, recursive: true);
+ }
+ }
+
+ public void TestSimpleNameAndNoPrefixAndNoSuffix()
+ {
+ ValidateNativeLibraryResolutions("{0}", "{0}", OS.Windows | OS.OSX | OS.Linux);
+ }
+
+ public void TestSimpleNameAndNoPrefixAndSuffix()
+ {
+ ValidateNativeLibraryResolutions("{0}.dll", "{0}", OS.Windows);
+ ValidateNativeLibraryResolutions("{0}.dylib", "{0}", OS.OSX);
+ ValidateNativeLibraryResolutions("{0}.so", "{0}", OS.Linux);
+ }
+
+ public void TestSimpleNameAndLibPrefixAndNoSuffix()
+ {
+ ValidateNativeLibraryResolutions("lib{0}", "{0}", OS.OSX | OS.Linux);
+ }
+
+ public void TestRelativeNameAndLibPrefixAndNoSuffix()
+ {
+ // The lib prefix is not added if the lookup is a relative path.
+ ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}", "{0}", 0);
+ }
+
+ public void TestSimpleNameAndLibPrefixAndSuffix()
+ {
+ ValidateNativeLibraryResolutions("lib{0}.dll", "{0}", 0);
+ ValidateNativeLibraryResolutions("lib{0}.dylib", "{0}", OS.OSX);
+ ValidateNativeLibraryResolutions("lib{0}.so", "{0}", OS.Linux);
+ }
+
+ public void TestNameWithSuffixAndNoPrefixAndNoSuffix()
+ {
+ ValidateNativeLibraryResolutions("{0}", "{0}.dll", 0);
+ ValidateNativeLibraryResolutions("{0}", "{0}.dylib", 0);
+ ValidateNativeLibraryResolutions("{0}", "{0}.so", 0);
+ }
+
+ public void TestNameWithSuffixAndNoPrefixAndSuffix()
+ {
+ ValidateNativeLibraryResolutions("{0}.dll", "{0}.dll", OS.Windows | OS.OSX | OS.Linux);
+ ValidateNativeLibraryResolutions("{0}.dylib", "{0}.dylib", OS.Windows | OS.OSX | OS.Linux);
+ ValidateNativeLibraryResolutions("{0}.so", "{0}.so", OS.Windows | OS.OSX | OS.Linux);
+ }
+
+ public void TestNameWithSuffixAndNoPrefixAndDoubleSuffix()
+ {
+ // Unixes add the suffix even if one is already present.
+ ValidateNativeLibraryResolutions("{0}.dll.dll", "{0}.dll", 0);
+ ValidateNativeLibraryResolutions("{0}.dylib.dylib", "{0}.dylib", OS.OSX);
+ ValidateNativeLibraryResolutions("{0}.so.so", "{0}.so", OS.Linux);
+ }
+
+ public void TestNameWithSuffixAndPrefixAndNoSuffix()
+ {
+ ValidateNativeLibraryResolutions("lib{0}", "{0}.dll", 0);
+ ValidateNativeLibraryResolutions("lib{0}", "{0}.dylib", 0);
+ ValidateNativeLibraryResolutions("lib{0}", "{0}.so", 0);
+ }
+
+ public void TestNameWithSuffixAndPrefixAndSuffix()
+ {
+ ValidateNativeLibraryResolutions("lib{0}.dll", "{0}.dll", OS.OSX | OS.Linux);
+ ValidateNativeLibraryResolutions("lib{0}.dylib", "{0}.dylib", OS.OSX | OS.Linux);
+ ValidateNativeLibraryResolutions("lib{0}.so", "{0}.so", OS.OSX | OS.Linux);
+ }
+
+ public void TestRelativeNameWithSuffixAndPrefixAndSuffix()
+ {
+ // The lib prefix is not added if the lookup is a relative path
+ ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.dll", "{0}.dll", 0);
+ ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.dylib", "{0}.dylib", 0);
+ ValidateNativeLibraryWithRelativeLookupResolutions("lib{0}.so", "{0}.so", 0);
+ }
+
+ public void TestNameWithPrefixAndNoPrefixAndNoSuffix()
+ {
+ ValidateNativeLibraryResolutions("{0}", "lib{0}", 0);
+ }
+
+ public void TestNameWithPrefixAndPrefixAndNoSuffix()
+ {
+ ValidateNativeLibraryResolutions("lib{0}", "lib{0}", OS.Windows | OS.OSX | OS.Linux);
+ }
+
+ public void TestNameWithPrefixAndNoPrefixAndSuffix()
+ {
+ ValidateNativeLibraryResolutions("{0}.dll", "lib{0}", 0);
+ ValidateNativeLibraryResolutions("{0}.dylib", "lib{0}", 0);
+ ValidateNativeLibraryResolutions("{0}.so", "lib{0}", 0);
+ }
+
+ public void TestNameWithPrefixAndPrefixAndSuffix()
+ {
+ ValidateNativeLibraryResolutions("lib{0}.dll", "lib{0}", OS.Windows);
+ ValidateNativeLibraryResolutions("lib{0}.dylib", "lib{0}", OS.OSX);
+ ValidateNativeLibraryResolutions("lib{0}.so", "lib{0}", OS.Linux);
+ }
+
+ public void TestWindowsAddsSuffixEvenWithOnePresent()
+ {
+ ValidateNativeLibraryResolutions("{0}.ext.dll", "{0}.ext", OS.Windows);
+ }
+
+ public void TestWindowsDoesntAddSuffixWhenExectubaleIsPresent()
+ {
+ ValidateNativeLibraryResolutions("{0}.dll.dll", "{0}.dll", 0);
+ ValidateNativeLibraryResolutions("{0}.dll.exe", "{0}.dll", 0);
+ ValidateNativeLibraryResolutions("{0}.exe.dll", "{0}.exe", 0);
+ ValidateNativeLibraryResolutions("{0}.exe.exe", "{0}.exe", 0);
+ }
+
+ private void TestLookupWithSuffixPrefersUnmodifiedSuffixOnUnixes()
+ {
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.dylib", "lib{0}.dylib", "{0}.dylib", OS.OSX);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.so", "lib{0}.so", "{0}.so", OS.Linux);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.dylib", "{0}.dylib.dylib", "{0}.dylib", OS.OSX);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.so", "{0}.so.so", "{0}.so", OS.Linux);
+ }
+
+ private void TestLookupWithoutSuffixPrefersWithSuffixOnUnixes()
+ {
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.dylib", "lib{0}.dylib", "{0}", OS.OSX);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.so", "lib{0}.so", "{0}", OS.Linux);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.dylib", "{0}", "{0}", OS.OSX);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.so", "{0}", "{0}", OS.Linux);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.dylib", "lib{0}", "{0}", OS.OSX);
+ ValidateNativeLibraryResolutionsWithTwoFiles("{0}.so", "lib{0}", "{0}", OS.Linux);
+ }
+
+ public void TestFullPathLookupWithMatchingFileName()
+ {
+ ValidateFullPathNativeLibraryResolutions("{0}", "{0}", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("{0}.dll", "{0}.dll", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("{0}.dylib", "{0}.dylib", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("{0}.so", "{0}.so", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("lib{0}", "lib{0}", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "lib{0}.dll", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "lib{0}.dylib", OS.Windows | OS.OSX | OS.Linux);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.so", "lib{0}.so", OS.Windows | OS.OSX | OS.Linux);
+ }
+
+ public void TestFullPathLookupWithDifferentFileName()
+ {
+ ValidateFullPathNativeLibraryResolutions("lib{0}", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("{0}.dll", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("{0}.dylib", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("{0}.so", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.so", "{0}", 0);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.dll", "{0}.dll", 0);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.dylib", "{0}.dylib", 0);
+ ValidateFullPathNativeLibraryResolutions("lib{0}.so", "{0}.so", 0);
+ }
+
+ [Flags]
+ private enum OS
+ {
+ Windows = 0x1,
+ OSX = 0x2,
+ Linux = 0x4
+ }
+
+ private void ValidateNativeLibraryResolutions(
+ string fileNamePattern,
+ string lookupNamePattern,
+ OS resolvesOnOSes)
+ {
+ string newDirectory = Guid.NewGuid().ToString().Substring(0, 8);
+ string nativeLibraryPath = CreateMockFile(Path.Combine(newDirectory, string.Format(fileNamePattern, "NativeLibrary")));
+ ValidateNativeLibraryResolutions(
+ Path.GetDirectoryName(nativeLibraryPath),
+ nativeLibraryPath,
+ string.Format(lookupNamePattern, "NativeLibrary"),
+ resolvesOnOSes);
+ }
+
+ private void ValidateNativeLibraryWithRelativeLookupResolutions(
+ string fileNamePattern,
+ string lookupNamePattern,
+ OS resolvesOnOSes)
+ {
+ string newDirectory = Guid.NewGuid().ToString().Substring(0, 8);
+ string nativeLibraryPath = CreateMockFile(Path.Combine(newDirectory, string.Format(fileNamePattern, "NativeLibrary")));
+ ValidateNativeLibraryResolutions(
+ Path.GetDirectoryName(Path.GetDirectoryName(nativeLibraryPath)),
+ nativeLibraryPath,
+ Path.Combine(newDirectory, string.Format(lookupNamePattern, "NativeLibrary")),
+ resolvesOnOSes);
+ }
+
+ private void ValidateFullPathNativeLibraryResolutions(
+ string fileNamePattern,
+ string lookupNamePattern,
+ OS resolvesOnOSes)
+ {
+ string newDirectory = Guid.NewGuid().ToString().Substring(0, 8);
+ string nativeLibraryPath = CreateMockFile(Path.Combine(newDirectory, string.Format(fileNamePattern, "NativeLibrary")));
+ ValidateNativeLibraryResolutions(
+ Path.GetDirectoryName(nativeLibraryPath),
+ nativeLibraryPath,
+ Path.Combine(Path.GetDirectoryName(nativeLibraryPath), string.Format(lookupNamePattern, "NativeLibrary")),
+ resolvesOnOSes);
+ }
+
+ private void ValidateNativeLibraryResolutionsWithTwoFiles(
+ string fileNameToResolvePattern,
+ string otherFileNamePattern,
+ string lookupNamePattern,
+ OS resolvesOnOSes)
+ {
+ string newDirectory = Guid.NewGuid().ToString().Substring(0, 8);
+ string nativeLibraryPath = CreateMockFile(Path.Combine(newDirectory, string.Format(fileNameToResolvePattern, "NativeLibrary")));
+ CreateMockFile(Path.Combine(newDirectory, string.Format(otherFileNamePattern, "NativeLibrary")));
+ ValidateNativeLibraryResolutions(
+ Path.GetDirectoryName(nativeLibraryPath),
+ nativeLibraryPath,
+ string.Format(lookupNamePattern, "NativeLibrary"),
+ resolvesOnOSes);
+ }
+
+ private void ValidateNativeLibraryResolutions(
+ string nativeLibraryPaths,
+ string expectedResolvedFilePath,
+ string lookupName,
+ OS resolvesOnOSes)
+ {
+ using (HostPolicyMock.Mock_corehost_resolve_componet_dependencies(
+ 0,
+ "",
+ $"{nativeLibraryPaths}",
+ ""))
+ {
+ AssemblyDependencyResolver resolver = new AssemblyDependencyResolver(
+ Path.Combine(TestBasePath, _componentAssemblyPath));
+
+ string result = resolver.ResolveUnmanagedDllToPath(lookupName);
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ if (resolvesOnOSes.HasFlag(OS.Windows))
+ {
+ Assert.Equal(expectedResolvedFilePath, result);
+ }
+ else
+ {
+ Assert.Null(result);
+ }
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ if (resolvesOnOSes.HasFlag(OS.OSX))
+ {
+ Assert.Equal(expectedResolvedFilePath, result);
+ }
+ else
+ {
+ Assert.Null(result);
+ }
+ }
+ else
+ {
+ if (resolvesOnOSes.HasFlag(OS.Linux))
+ {
+ Assert.Equal(expectedResolvedFilePath, result);
+ }
+ else
+ {
+ Assert.Null(result);
+ }
+ }
+ }
+ }
+
+ private string CreateMockFile(string relativePath)
+ {
+ string fullPath = Path.Combine(_componentDirectory, relativePath);
+ if (!File.Exists(fullPath))
+ {
+ string directory = Path.GetDirectoryName(fullPath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ File.WriteAllText(fullPath, "Mock file");
+ }
+
+ return fullPath;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace AssemblyDependencyResolverTests
+{
+ class TestBase
+ {
+ protected string TestBasePath { get; private set; }
+ protected string BinaryBasePath { get; private set; }
+ protected string CoreRoot { get; private set; }
+
+ protected virtual void Initialize()
+ {
+ }
+
+ protected virtual void Cleanup()
+ {
+ }
+
+ public static int RunTests(params Type[] testTypes)
+ {
+ int result = 100;
+ foreach (Type testType in testTypes)
+ {
+ int testResult = RunTestsForType(testType);
+ if (testResult != 100)
+ {
+ result = testResult;
+ }
+ }
+
+ return result;
+ }
+
+ private static int RunTestsForType(Type testType)
+ {
+ string testBasePath = Path.GetDirectoryName(testType.Assembly.Location);
+
+ TestBase runner = (TestBase)Activator.CreateInstance(testType);
+ runner.TestBasePath = testBasePath;
+ runner.BinaryBasePath = Path.GetDirectoryName(testBasePath);
+ runner.CoreRoot = GetCoreRoot();
+
+ try
+ {
+ runner.Initialize();
+
+ runner.RunTestsForInstance(runner);
+ return runner._retValue;
+ }
+ finally
+ {
+ runner.Cleanup();
+ }
+ }
+
+ private int _retValue = 100;
+ private void RunSingleTest(Action test, string testName = null)
+ {
+ testName = testName ?? test.Method.Name;
+
+ try
+ {
+ Console.WriteLine($"{testName} Start");
+ test();
+ Console.WriteLine($"{testName} PASSED.");
+ }
+ catch (Exception exe)
+ {
+ Console.WriteLine($"{testName} FAILED:");
+ Console.WriteLine(exe.ToString());
+ _retValue = -1;
+ }
+ }
+
+ private void RunTestsForInstance(object testClass)
+ {
+ foreach (MethodInfo m in testClass.GetType()
+ .GetMethods(BindingFlags.Instance | BindingFlags.Public)
+ .Where(m => m.Name.StartsWith("Test") && m.GetParameters().Length == 0))
+ {
+ RunSingleTest(() => m.Invoke(testClass, new object[0]), m.Name);
+ }
+ }
+
+ private static string GetCoreRoot()
+ {
+ string value = Environment.GetEnvironmentVariable("CORE_ROOT");
+ if (value == null)
+ {
+ value = Directory.GetCurrentDirectory();
+ }
+
+ return value;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+namespace AssemblyDependencyResolverTests
+{
+ class XPlatformUtils
+ {
+#if WINDOWS
+ public const string NativeLibraryPrefix = "";
+ public const string NativeLibrarySuffix = ".dll";
+#else
+ public const string NativeLibraryPrefix = "lib";
+#if OSX
+ public const string NativeLibrarySuffix = ".dylib";
+#else
+ public const string NativeLibrarySuffix = ".so";
+#endif
+#endif
+
+ public static string GetStandardNativeLibraryFileName(string simpleName)
+ {
+ return NativeLibraryPrefix + simpleName + NativeLibrarySuffix;
+ }
+ }
+}