-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathTaskLoader.cs
More file actions
204 lines (182 loc) · 8.52 KB
/
Copy pathTaskLoader.cs
File metadata and controls
204 lines (182 loc) · 8.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Build.Framework;
#if FEATURE_APPDOMAIN
using System;
using System.Reflection;
using Microsoft.Build.Shared.Debugging;
#endif
namespace Microsoft.Build.Shared
{
/// <summary>
/// Class for loading tasks
/// </summary>
internal static class TaskLoader
{
#if FEATURE_APPDOMAIN
/// <summary>
/// For saving the assembly that was loaded by the TypeLoader
/// We only use this when the assembly failed to load properly into the appdomain
/// </summary>
private static LoadedType? s_resolverLoadedType;
#endif
/// <summary>
/// Delegate for logging task loading errors.
/// </summary>
internal delegate void LogError(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs);
/// <summary>
/// Creates an ITask instance and returns it.
/// </summary>
#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter
internal static ITask? CreateTask(
LoadedType loadedType,
string taskName,
string taskLocation,
int taskLine,
int taskColumn,
LogError logError,
TaskEnvironment? taskEnvironment,
#if FEATURE_APPDOMAIN
AppDomainSetup appDomainSetup,
Action<AppDomain> appDomainCreated,
#endif
bool isOutOfProc
#if FEATURE_APPDOMAIN
, out AppDomain? taskAppDomain
#endif
)
#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter
{
#if FEATURE_APPDOMAIN
bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute;
s_resolverLoadedType = null;
taskAppDomain = null;
ITask? taskInstanceInOtherAppDomain = null;
#endif
try
{
#if FEATURE_APPDOMAIN
if (separateAppDomain)
{
if (!loadedType.IsMarshalByRef)
{
logError(
taskLocation,
taskLine,
taskColumn,
"TaskNotMarshalByRef",
taskName);
return null;
}
else
{
// Our task depend on this name to be precisely that, so if you change it make sure
// you also change the checks in the tasks run in separate AppDomains. Better yet, just don't change it.
// Make sure we copy the appdomain configuration and send it to the appdomain we create so that if the creator of the current appdomain
// has done the binding redirection in code, that we will get those settings as well.
AppDomainSetup appDomainInfo = new AppDomainSetup();
// Get the current app domain setup settings
byte[] currentAppdomainBytes = appDomainSetup.GetConfigurationBytes();
// Apply the appdomain settings to the new appdomain before creating it
appDomainInfo.SetConfigurationBytes(currentAppdomainBytes);
if (BuildEnvironmentHelper.Instance.RunningTests)
{
// Prevent the new app domain from looking in the VS test runner location. If this
// is not done, we will not be able to find Microsoft.Build.* assemblies.
appDomainInfo.ApplicationBase = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory;
appDomainInfo.ConfigurationFile = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile;
}
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver;
s_resolverLoadedType = loadedType;
taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo);
if (loadedType.LoadedAssembly != null)
{
taskAppDomain.Load(loadedType.LoadedAssemblyName);
}
// Hook up last minute dumping of any exceptions
taskAppDomain.UnhandledException += DebugUtils.UnhandledExceptionHandler;
appDomainCreated?.Invoke(taskAppDomain);
}
}
else
#endif
{
// perf improvement for the same appdomain case - we already have the type object
// and don't want to go through reflection to recreate it from the name.
// LoadedType owns instantiation: it invokes the TaskEnvironment-accepting constructor when
// the task declares one (so the task can compute environment-dependent defaults during
// construction) or the parameterless one otherwise, through a cached, Native AOT friendly
// mechanism. The engine still assigns the TaskEnvironment property separately afterward.
return loadedType.CreateInstance(taskEnvironment);
}
#if FEATURE_APPDOMAIN
if (loadedType.Assembly.AssemblyFile != null)
{
taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName);
// this will force evaluation of the task class type and try to load the task assembly
Type taskType = taskInstanceInOtherAppDomain.GetType();
// If the types don't match, we have a problem. It means that our AppDomain was able to load
// a task assembly using Load, and loaded a different one. I don't see any other choice than
// to fail here.
if (taskType != loadedType.Type)
{
logError(
taskLocation,
taskLine,
taskColumn,
"ConflictingTaskAssembly",
loadedType.Assembly.AssemblyFile,
loadedType.Type.Assembly.Location);
taskInstanceInOtherAppDomain = null;
}
}
else
{
taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.Assembly.FullName, loadedType.Type.FullName);
}
return taskInstanceInOtherAppDomain;
#endif
}
finally
{
#if FEATURE_APPDOMAIN
// Don't leave appdomains open
if (taskAppDomain != null && taskInstanceInOtherAppDomain == null)
{
AppDomain.Unload(taskAppDomain);
RemoveAssemblyResolver();
}
#endif
}
}
#if FEATURE_APPDOMAIN
/// <summary>
/// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help
/// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it
/// </summary>
internal static Assembly? AssemblyResolver(object sender, ResolveEventArgs args)
{
if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName?.FullName, StringComparison.OrdinalIgnoreCase))
{
if (s_resolverLoadedType == null || s_resolverLoadedType.Path == null)
{
return null;
}
return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path);
}
return null;
}
/// <summary>
/// Check if we added a resolver and remove it
/// </summary>
internal static void RemoveAssemblyResolver()
{
if (s_resolverLoadedType != null)
{
AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolver;
s_resolverLoadedType = null;
}
}
#endif
}
}