Skip to content
This repository was archived by the owner on Feb 23, 2021. It is now read-only.

Commit 1e72cc6

Browse files
committed
Use a custom LoadContext to unify types between ProjectLoadContext and Default load context
Works around https://github.com/dotnet/cli/issues/4316
1 parent 25a292e commit 1e72cc6

2 files changed

Lines changed: 200 additions & 4 deletions

File tree

src/Microsoft.AspNetCore.Razor.Tools/Internal/PackageOnlyResolveTagHelpersRunCommand.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ public static bool TryPackageOnlyTagHelperResolution(
8181
}
8282
}
8383

84-
var loadContext = projectContext.CreateLoadContext(
85-
projectContext.RuntimeIdentifier,
86-
configuration,
87-
outputPath: null);
84+
var loadContext = new RazorProjectLoadContext(projectContext, configuration);
8885
var runner = new PackageOnlyResolveTagHelpersRunCommand(loadContext)
8986
{
9087
AssemblyNamesArgument = assemblyNamesArgument,
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#if NETCOREAPP1_0
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Runtime.InteropServices;
11+
using System.Runtime.Loader;
12+
using Microsoft.DotNet.ProjectModel.Compilation;
13+
using Microsoft.Extensions.DependencyModel;
14+
15+
namespace Microsoft.DotNet.ProjectModel.Loader
16+
{
17+
public class RazorProjectLoadContext : AssemblyLoadContext
18+
{
19+
private readonly IDictionary<AssemblyName, string> _assemblyPaths;
20+
private readonly IDictionary<string, string> _nativeLibraries;
21+
private readonly string _searchPath;
22+
23+
private static readonly string[] NativeLibraryExtensions;
24+
private static readonly string[] ManagedAssemblyExtensions = new[]
25+
{
26+
".dll",
27+
".ni.dll",
28+
".exe",
29+
".ni.exe"
30+
};
31+
32+
static RazorProjectLoadContext()
33+
{
34+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
35+
{
36+
NativeLibraryExtensions = new[] { ".dll" };
37+
}
38+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
39+
{
40+
NativeLibraryExtensions = new[] { ".dylib" };
41+
}
42+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
43+
{
44+
NativeLibraryExtensions = new[] { ".so" };
45+
}
46+
else
47+
{
48+
NativeLibraryExtensions = new string[0];
49+
}
50+
}
51+
52+
public RazorProjectLoadContext(
53+
ProjectContext context,
54+
string configuration)
55+
{
56+
var exporter = context.CreateExporter(configuration);
57+
_assemblyPaths = new Dictionary<AssemblyName, string>(AssemblyNameComparer.OrdinalIgnoreCase);
58+
_nativeLibraries = new Dictionary<string, string>();
59+
var rids = DependencyContext.Default?.RuntimeGraph ?? Enumerable.Empty<RuntimeFallbacks>();
60+
var runtimeIdentifier = context.RuntimeIdentifier;
61+
var fallbacks = rids.FirstOrDefault(r => r.Runtime.Equals(runtimeIdentifier));
62+
63+
foreach (var export in exporter.GetAllExports())
64+
{
65+
// Process managed assets
66+
var group = string.IsNullOrEmpty(runtimeIdentifier) ?
67+
export.RuntimeAssemblyGroups.GetDefaultGroup() :
68+
GetGroup(export.RuntimeAssemblyGroups, runtimeIdentifier, fallbacks);
69+
if (group != null)
70+
{
71+
foreach (var asset in group.Assets)
72+
{
73+
_assemblyPaths[asset.GetAssemblyName()] = asset.ResolvedPath;
74+
}
75+
}
76+
77+
// Process native assets
78+
group = string.IsNullOrEmpty(runtimeIdentifier) ?
79+
export.NativeLibraryGroups.GetDefaultGroup() :
80+
GetGroup(export.NativeLibraryGroups, runtimeIdentifier, fallbacks);
81+
if (group != null)
82+
{
83+
foreach (var asset in group.Assets)
84+
{
85+
_nativeLibraries[asset.Name] = asset.ResolvedPath;
86+
}
87+
}
88+
89+
// Process resource assets
90+
foreach (var asset in export.ResourceAssemblies)
91+
{
92+
var name = asset.Asset.GetAssemblyName();
93+
name.CultureName = asset.Locale;
94+
_assemblyPaths[name] = asset.Asset.ResolvedPath;
95+
}
96+
}
97+
98+
_searchPath = context.GetOutputPaths(configuration, outputPath: null).CompilationOutputPath;
99+
}
100+
101+
protected override Assembly Load(AssemblyName assemblyName)
102+
{
103+
try
104+
{
105+
var assembly = Default.LoadFromAssemblyName(assemblyName);
106+
if (assembly != null)
107+
{
108+
return assembly;
109+
}
110+
}
111+
catch (FileNotFoundException)
112+
{
113+
}
114+
115+
string path;
116+
if (_assemblyPaths.TryGetValue(assemblyName, out path) || SearchForLibrary(ManagedAssemblyExtensions, assemblyName.Name, out path))
117+
{
118+
return LoadFromAssemblyPath(path);
119+
}
120+
121+
return null;
122+
}
123+
124+
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
125+
{
126+
string path;
127+
if (_nativeLibraries.TryGetValue(unmanagedDllName, out path) || SearchForLibrary(NativeLibraryExtensions, unmanagedDllName, out path))
128+
{
129+
return LoadUnmanagedDllFromPath(path);
130+
}
131+
132+
return LoadUnmanagedDll(unmanagedDllName);
133+
}
134+
135+
private static LibraryAssetGroup GetGroup(IEnumerable<LibraryAssetGroup> groups, string runtimeIdentifier, RuntimeFallbacks fallbacks)
136+
{
137+
IEnumerable<string> rids = new[] { runtimeIdentifier };
138+
if (fallbacks != null)
139+
{
140+
rids = Enumerable.Concat(rids, fallbacks.Fallbacks);
141+
}
142+
143+
foreach (var rid in rids)
144+
{
145+
var group = groups.GetRuntimeGroup(rid);
146+
if (group != null)
147+
{
148+
return group;
149+
}
150+
}
151+
return null;
152+
}
153+
154+
private bool SearchForLibrary(string[] extensions, string name, out string path)
155+
{
156+
foreach (var extension in extensions)
157+
{
158+
var candidate = Path.Combine(_searchPath, name + extension);
159+
if (File.Exists(candidate))
160+
{
161+
path = candidate;
162+
return true;
163+
}
164+
}
165+
166+
path = null;
167+
return false;
168+
}
169+
170+
private class AssemblyNameComparer : IEqualityComparer<AssemblyName>
171+
{
172+
public static readonly IEqualityComparer<AssemblyName> OrdinalIgnoreCase = new AssemblyNameComparer();
173+
174+
private AssemblyNameComparer()
175+
{
176+
}
177+
178+
public bool Equals(AssemblyName x, AssemblyName y)
179+
{
180+
// Ignore case because that's what Assembly.Load does.
181+
return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) &&
182+
string.Equals(x.CultureName ?? string.Empty, y.CultureName ?? string.Empty, StringComparison.Ordinal);
183+
}
184+
185+
public int GetHashCode(AssemblyName obj)
186+
{
187+
var hashCode = 0;
188+
if (obj.Name != null)
189+
{
190+
hashCode ^= obj.Name.GetHashCode();
191+
}
192+
193+
hashCode ^= (obj.CultureName ?? string.Empty).GetHashCode();
194+
return hashCode;
195+
}
196+
}
197+
}
198+
}
199+
#endif

0 commit comments

Comments
 (0)