Skip to content

Commit ad6e6ae

Browse files
github-actions[bot]fabiocavliliankasem
authored
[v3.x] Enhancing native dependency resolution for Linux/OSX (#8375)
* Enhancing native dependency resolution for Linux/OSX * Fix systemruntime info and spelling * Remove StringComparison argument from Contains Co-authored-by: Fabio Cavalcante <[email protected]> Co-authored-by: Lilian Kasem <[email protected]>
1 parent 1b13f11 commit ad6e6ae

File tree

7 files changed

+188
-69
lines changed

7 files changed

+188
-69
lines changed

src/WebJobs.Script/Description/DotNet/DynamicFunctionAssemblyLoadContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ protected override Assembly OnResolvingAssembly(AssemblyLoadContext context, Ass
3737

3838
if (assembly == null)
3939
{
40-
_logger.AssemblyDynamiclyResolutionFailure(assemblyName.FullName, _functionMetadata.Name);
40+
_logger.AssemblyDynamicallyResolutionFailure(assemblyName.FullName, _functionMetadata.Name);
4141
}
4242

4343
return assembly;
@@ -61,7 +61,7 @@ protected override Assembly Load(AssemblyName assemblyName)
6161
assembly = _metadataResolver?.ResolveAssembly(assemblyName, this);
6262
if (assembly == null)
6363
{
64-
_logger.AssemblyDynamiclyResolved(assemblyName.FullName, _functionMetadata.Name);
64+
_logger.AssemblyDynamicallyResolved(assemblyName.FullName, _functionMetadata.Name);
6565
}
6666

6767
return assembly;

src/WebJobs.Script/Description/DotNet/FunctionAssemblyLoadContext.cs

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -440,12 +440,14 @@ protected virtual Assembly OnResolvingAssembly(AssemblyLoadContext arg1, Assembl
440440

441441
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
442442
{
443-
string fileName = GetUnmanagedLibraryFileName(unmanagedDllName);
444-
string filePath = GetRuntimeNativeAssetPath(fileName);
445-
446-
if (filePath != null)
443+
foreach (var fileName in GetUnmanagedLibraryFileNames(unmanagedDllName, SystemRuntimeInformation.Instance.GetOSPlatform()))
447444
{
448-
return LoadUnmanagedDllFromPath(filePath);
445+
string filePath = GetRuntimeNativeAssetPath(fileName);
446+
447+
if (filePath != null)
448+
{
449+
return LoadUnmanagedDllFromPath(filePath);
450+
}
449451
}
450452

451453
return base.LoadUnmanagedDll(unmanagedDllName);
@@ -481,45 +483,102 @@ internal static string ProbeForNativeAsset(IList<string> probingPaths, string as
481483

482484
List<string> rids = DependencyHelper.GetRuntimeFallbacks();
483485

484-
string result = rids.Select(r => Path.Combine(runtimesPath, r, ridSubFolder, assetFileName))
485-
.Union(probingPaths.Select(p => Path.Combine(p, assetFileName)))
486-
.FirstOrDefault(p => fileBase.Exists(p));
486+
foreach (string rid in rids)
487+
{
488+
string path = Path.Combine(runtimesPath, rid, ridSubFolder, assetFileName);
489+
if (fileBase.Exists(path))
490+
{
491+
return path;
492+
}
493+
}
487494

488-
return result;
495+
foreach (var probingPath in probingPaths)
496+
{
497+
string path = Path.Combine(probingPath, assetFileName);
498+
499+
if (fileBase.Exists(path))
500+
{
501+
return path;
502+
}
503+
}
504+
505+
return null;
489506
}
490507

491-
internal string GetUnmanagedLibraryFileName(string unmanagedLibraryName)
508+
internal static IEnumerable<string> GetUnmanagedLibraryFileNames(string unmanagedLibraryName, OSPlatform platform)
492509
{
493510
// We need to properly resolve the native library in different platforms:
494-
// - Windows will append the '.DLL' extension to the name
495-
// - Linux uses the 'lib' prefix and '.so' suffix. The version may also be appended to the suffix
496-
// - macOS uses the 'lib' prefix '.dylib'
497-
// To handle the different scenarios described above, we'll just have a pattern that gives us the ability
498-
// to match the variations across different platforms. If needed, we can expand this in the future to have
499-
// logic specific to the platform we're running under.
500-
501-
string fileName = null;
502-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
511+
// Windows:
512+
// - Will append the '.dll' extension to the name, if it does not already have the extension.
513+
// Linux and macOS:
514+
// - Returns the following, in order (where prefix is 'lib' and suffix is '.so' for Linux and '.dylib' for macOS):
515+
// - libname + suffix
516+
// - prefix + libname + suffix **
517+
// - libname
518+
// - prefix + libname *
519+
// On Linux only, if the library name ends with the suffix or contains the suffix + '.' (i.e. .so or .so.)
520+
// - Returns the following, in order (where prefix is 'lib' and suffix is '.so'):
521+
// - libname
522+
// - prefix + libname *
523+
// - libname + suffix
524+
// - prefix + libname + suffix **
525+
// * Will only be returned if the path delimiter is not present in the library name
526+
// ** Would typically have the same path delimiter check, but we maintain this here for backwards compatibility
527+
528+
if (platform == OSPlatform.Windows)
503529
{
504530
if (unmanagedLibraryName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
505531
{
506-
fileName = unmanagedLibraryName;
532+
yield return unmanagedLibraryName;
507533
}
508534
else
509535
{
510-
fileName = unmanagedLibraryName + ".dll";
536+
yield return unmanagedLibraryName + ".dll";
511537
}
512538
}
513-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
539+
else
514540
{
515-
fileName = "lib" + unmanagedLibraryName + ".dylib";
541+
const string prefix = "lib";
542+
string suffix = platform == OSPlatform.Linux
543+
? ".so"
544+
: ".dylib";
545+
546+
foreach (var name in GetUnixUnmanagedLibraryFileNames(unmanagedLibraryName, prefix, suffix, platform))
547+
{
548+
yield return name;
549+
}
516550
}
517-
else
551+
}
552+
553+
private static IEnumerable<string> GetUnixUnmanagedLibraryFileNames(string unmanagedLibraryName, string prefix, string suffix, OSPlatform platform)
554+
{
555+
bool containsPathDelimiter = unmanagedLibraryName.Contains(Path.DirectorySeparatorChar);
556+
bool containsExtension = platform == OSPlatform.Linux
557+
&& (unmanagedLibraryName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)
558+
|| unmanagedLibraryName.ToLower().Contains(suffix));
559+
560+
if (containsExtension)
518561
{
519-
fileName = "lib" + unmanagedLibraryName + ".so";
562+
yield return unmanagedLibraryName;
563+
564+
if (!containsPathDelimiter)
565+
{
566+
yield return prefix + unmanagedLibraryName;
567+
}
520568
}
521569

522-
return fileName;
570+
yield return unmanagedLibraryName + suffix;
571+
yield return prefix + unmanagedLibraryName + suffix;
572+
573+
if (!containsExtension)
574+
{
575+
yield return unmanagedLibraryName;
576+
577+
if (!containsPathDelimiter)
578+
{
579+
yield return prefix + unmanagedLibraryName;
580+
}
581+
}
523582
}
524583

525584
protected static string ResolveFunctionBaseProbingPath()

src/WebJobs.Script/Diagnostics/Extensions/DescriptionLoggerExtension.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ public static class DescriptionLoggerExtension
1010
{
1111
// EventId range is 1-99
1212

13-
private static readonly Action<ILogger, string, string, Exception> _assemblyDynamiclyResolved =
13+
private static readonly Action<ILogger, string, string, Exception> _assemblyDynamicallyResolved =
1414
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(1, nameof(AssemblyResolved)),
1515
"Resolved assembly '{assemblyName}' in the function load context using the dynamic resolver for function '{functionName}'");
1616

17-
private static readonly Action<ILogger, string, string, Exception> _assemblyDynamiclyResolutionFailure =
17+
private static readonly Action<ILogger, string, string, Exception> _assemblyDynamicallyResolutionFailure =
1818
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(2, nameof(AssemblyResolutionFailure)),
1919
"Attempt to resolve assembly '{assemblyName}' failed in the function load context using the dynamic resolver for function '{functionName}'");
2020

@@ -26,14 +26,14 @@ public static class DescriptionLoggerExtension
2626
LoggerMessage.Define<string, string>(LogLevel.Trace, new EventId(4, nameof(AssemblyResolutionFailure)),
2727
"Attempt to resolve assembly '{assemblyName}' failed in the function load context using the resolver '{assemblyResolverName}'");
2828

29-
public static void AssemblyDynamiclyResolved(this ILogger logger, string assemblyName, string functionName)
29+
public static void AssemblyDynamicallyResolved(this ILogger logger, string assemblyName, string functionName)
3030
{
31-
_assemblyDynamiclyResolved(logger, assemblyName, functionName, null);
31+
_assemblyDynamicallyResolved(logger, assemblyName, functionName, null);
3232
}
3333

34-
public static void AssemblyDynamiclyResolutionFailure(this ILogger logger, string assemblyName, string functionName)
34+
public static void AssemblyDynamicallyResolutionFailure(this ILogger logger, string assemblyName, string functionName)
3535
{
36-
_assemblyDynamiclyResolutionFailure(logger, assemblyName, functionName, null);
36+
_assemblyDynamicallyResolutionFailure(logger, assemblyName, functionName, null);
3737
}
3838

3939
public static void AssemblyResolved(this ILogger logger, string assemblyName, string assemblyResolverName)

src/WebJobs.Script/Workers/RuntimeInformation/ISystemRuntimeInformation.cs renamed to src/WebJobs.Script/RuntimeInformation/ISystemRuntimeInformation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
using System.Runtime.InteropServices;
55

6-
namespace Microsoft.Azure.WebJobs.Script.Workers
6+
namespace Microsoft.Azure.WebJobs.Script
77
{
88
public interface ISystemRuntimeInformation
99
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Microsoft.Azure.WebJobs.Script
8+
{
9+
public class SystemRuntimeInformation : ISystemRuntimeInformation
10+
{
11+
private static OSPlatform? _platform;
12+
private static Lazy<SystemRuntimeInformation> _runtimeInformationInstance = new Lazy<SystemRuntimeInformation>(() => new SystemRuntimeInformation());
13+
14+
public static ISystemRuntimeInformation Instance => _runtimeInformationInstance.Value;
15+
16+
public Architecture GetOSArchitecture()
17+
{
18+
return RuntimeInformation.OSArchitecture;
19+
}
20+
21+
public OSPlatform GetOSPlatform()
22+
{
23+
if (_platform == null)
24+
{
25+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
26+
{
27+
_platform = OSPlatform.Windows;
28+
}
29+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
30+
{
31+
_platform = OSPlatform.OSX;
32+
}
33+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
34+
{
35+
_platform = OSPlatform.Linux;
36+
}
37+
}
38+
39+
return _platform.Value;
40+
}
41+
}
42+
}

src/WebJobs.Script/Workers/RuntimeInformation/SystemRuntimeInformation.cs

Lines changed: 0 additions & 34 deletions
This file was deleted.

test/WebJobs.Script.Tests/Description/DotNet/FunctionAssemblyLoadContextTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,57 @@ public void ProbeForNativeAssets_FindsAsset(string assetPath)
139139
string result = FunctionAssemblyLoadContext.ProbeForNativeAsset(probingPaths, "assembly.dll", mockFile.Object);
140140
Assert.Equal(assetPath, result);
141141
}
142+
143+
[Theory]
144+
[MemberData(nameof(UnmanagedLibraryNames))]
145+
public void GetUnmanagedLibraryFileNames_ReturnsExpectedResults(string libName, string[] expectedResults, OSPlatform platform)
146+
{
147+
var result = FunctionAssemblyLoadContext.GetUnmanagedLibraryFileNames(libName, platform);
148+
149+
Assert.Equal(expectedResults, result);
150+
}
151+
152+
public static IEnumerable<object[]> UnmanagedLibraryNames()
153+
{
154+
return new[]
155+
{
156+
new object[]
157+
{
158+
"testdep",
159+
new string[] { "testdep.dll" },
160+
OSPlatform.Windows
161+
},
162+
new object[]
163+
{
164+
"testdep.dll",
165+
new string[] { "testdep.dll" },
166+
OSPlatform.Windows
167+
},
168+
new object[]
169+
{
170+
"testdep",
171+
new string[] { "testdep.so", "libtestdep.so", "testdep", "libtestdep" },
172+
OSPlatform.Linux
173+
},
174+
new object[]
175+
{
176+
"testdep",
177+
new string[] { "testdep.dylib", "libtestdep.dylib", "testdep", "libtestdep" },
178+
OSPlatform.OSX
179+
},
180+
new object[]
181+
{
182+
"testdep.so",
183+
new string[] { "testdep.so", "libtestdep.so", "testdep.so.so", "libtestdep.so.so" },
184+
OSPlatform.Linux
185+
},
186+
new object[]
187+
{
188+
"testdep.so.6",
189+
new string[] { "testdep.so.6", "libtestdep.so.6", "testdep.so.6.so", "libtestdep.so.6.so" },
190+
OSPlatform.Linux
191+
},
192+
};
193+
}
142194
}
143195
}

0 commit comments

Comments
 (0)