1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>Class implementing an out-of-proc node for executing tasks inside an AppDomain.</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Diagnostics;
12 using System.Diagnostics.CodeAnalysis;
13 using System.IO;
14 using System.Linq;
15 using System.Text;
16 using System.Globalization;
17 using System.Threading;
18 using System.Reflection;
19 using System.Runtime.CompilerServices;
20 
21 using Microsoft.Build.BackEnd;
22 using Microsoft.Build.Execution;
23 using Microsoft.Build.Framework;
24 using Microsoft.Build.Internal;
25 using Microsoft.Build.Shared;
26 #if FEATURE_APPDOMAIN
27 using System.Runtime.Remoting;
28 #endif
29 
30 namespace Microsoft.Build.CommandLine
31 {
32     /// <summary>
33     /// Class for executing a task in an AppDomain
34     /// </summary>
35     [Serializable]
36     internal class OutOfProcTaskAppDomainWrapperBase
37 #if FEATURE_APPDOMAIN
38         : MarshalByRefObject
39 #endif
40     {
41         /// <summary>
42         /// This is the actual user task whose instance we will create and invoke Execute
43         /// </summary>
44         private ITask wrappedTask;
45 
46 #if FEATURE_APPDOMAIN
47         /// <summary>
48         /// This is an appDomain instance if any is created for running this task
49         /// </summary>
50         /// <comments>
51         /// TaskAppDomain's non-serializability should never be an issue since even if we start running the wrapper
52         /// in a separate appdomain, we will not be trying to load the task on one side of the serialization
53         /// boundary and run it on the other.
54         /// </comments>
55         [NonSerialized]
56         private AppDomain _taskAppDomain;
57 #endif
58 
59         /// <summary>
60         /// Need to keep the build engine around in order to log from the task loader.
61         /// </summary>
62         private IBuildEngine buildEngine;
63 
64         /// <summary>
65         /// Need to keep track of the task name also so that we can log valid information
66         /// from the task loader.
67         /// </summary>
68         private string taskName;
69 
70         /// <summary>
71         /// This is the actual user task whose instance we will create and invoke Execute
72         /// </summary>
73         public ITask WrappedTask
74         {
75             get { return wrappedTask; }
76         }
77 
78         /// <summary>
79         /// We have a cancel already requested
80         /// This can happen before we load the module and invoke execute.
81         /// </summary>
82         internal bool CancelPending
83         {
84             get;
85             set;
86         }
87 
88         /// <summary>
89         /// This is responsible for invoking Execute on the Task
90         /// Any method calling ExecuteTask must remember to call CleanupTask
91         /// </summary>
92         /// <remarks>
93         /// We also allow the Task to have a reference to the BuildEngine by design
94         /// at ITask.BuildEngine
95         /// </remarks>
96         /// <param name="oopTaskHostNode">The OutOfProcTaskHostNode as the BuildEngine</param>
97         /// <param name="taskName">The name of the task to be executed</param>
98         /// <param name="taskLocation">The path of the task binary</param>
99         /// <param name="taskFile">The path to the project file in which the task invocation is located.</param>
100         /// <param name="taskLine">The line in the project file where the task invocation is located.</param>
101         /// <param name="taskColumn">The column in the project file where the task invocation is located.</param>
102         /// <param name="appDomainSetup">The AppDomainSetup that we want to use to launch our AppDomainIsolated tasks</param>
103         /// <param name="taskParams">Parameters that will be passed to the task when created</param>
104         /// <returns>Task completion result showing success, failure or if there was a crash</returns>
ExecuteTask( IBuildEngine oopTaskHostNode, string taskName, string taskLocation, string taskFile, int taskLine, int taskColumn, AppDomainSetup appDomainSetup, IDictionary<string, TaskParameter> taskParams )105         internal OutOfProcTaskHostTaskResult ExecuteTask
106             (
107                 IBuildEngine oopTaskHostNode,
108                 string taskName,
109                 string taskLocation,
110                 string taskFile,
111                 int taskLine,
112                 int taskColumn,
113 #if FEATURE_APPDOMAIN
114                 AppDomainSetup appDomainSetup,
115 #endif
116                 IDictionary<string, TaskParameter> taskParams
117             )
118         {
119             buildEngine = oopTaskHostNode;
120             this.taskName = taskName;
121 
122 #if FEATURE_APPDOMAIN
123             _taskAppDomain = null;
124 #endif
125             wrappedTask = null;
126 
127             LoadedType taskType = null;
128             try
129             {
130                 TypeLoader typeLoader = new TypeLoader(TaskLoader.IsTaskClass);
131                 taskType = typeLoader.Load(taskName, AssemblyLoadInfo.Create(null, taskLocation));
132             }
133             catch (Exception e)
134             {
135                 if (ExceptionHandling.IsCriticalException(e))
136                 {
137                     throw;
138                 }
139 
140                 Exception exceptionToReturn = e;
141 
142                 // If it's a TargetInvocationException, we only care about the contents of the inner exception,
143                 // so just save that instead.
144                 if (e is TargetInvocationException)
145                 {
146                     exceptionToReturn = e.InnerException;
147                 }
148 
149                 return new OutOfProcTaskHostTaskResult
150                                 (
151                                     TaskCompleteType.CrashedDuringInitialization,
152                                     exceptionToReturn,
153                                     "TaskInstantiationFailureError",
154                                     new string[] { taskName, taskLocation, String.Empty }
155                                 );
156             }
157 
158             OutOfProcTaskHostTaskResult taskResult;
159             if (taskType.HasSTAThreadAttribute())
160             {
161 #if FEATURE_APARTMENT_STATE
162                 taskResult = InstantiateAndExecuteTaskInSTAThread(oopTaskHostNode, taskType, taskName, taskLocation, taskFile, taskLine, taskColumn,
163 #if FEATURE_APPDOMAIN
164                     appDomainSetup,
165 #endif
166                     taskParams);
167 #else
168                 return new OutOfProcTaskHostTaskResult
169                                                 (
170                                                     TaskCompleteType.CrashedDuringInitialization,
171                                                     null,
172                                                     "TaskInstantiationFailureNotSupported",
173                                                     new string[] { taskName, taskLocation, typeof(RunInSTAAttribute).FullName }
174                                                 );
175 #endif
176             }
177             else
178             {
179                 taskResult = InstantiateAndExecuteTask(oopTaskHostNode, taskType, taskName, taskLocation, taskFile, taskLine, taskColumn,
180 #if FEATURE_APPDOMAIN
181                     appDomainSetup,
182 #endif
183                     taskParams);
184             }
185 
186             return taskResult;
187         }
188 
189         /// <summary>
190         /// This is responsible for cleaning up the task after the OutOfProcTaskHostNode has gathered everything it needs from this execution
191         /// For example: We will need to hold on new AppDomains created until we finish getting all outputs from the task
192         /// Add any other cleanup tasks here. Any method calling ExecuteTask must remember to call CleanupTask.
193         /// </summary>
CleanupTask()194         internal void CleanupTask()
195         {
196 #if FEATURE_APPDOMAIN
197             if (_taskAppDomain != null)
198             {
199                 AppDomain.Unload(_taskAppDomain);
200             }
201 
202             TaskLoader.RemoveAssemblyResolver();
203 #endif
204             wrappedTask = null;
205         }
206 
207 #if FEATURE_APARTMENT_STATE
208         /// <summary>
209         /// Execute a task on the STA thread.
210         /// </summary>
211         /// <comment>
212         /// STA thread launching code lifted from XMakeBuildEngine\BackEnd\Components\RequestBuilder\TaskBuilder.cs, ExecuteTaskInSTAThread method.
213         /// Any bug fixes made to this code, please ensure that you also fix that code.
214         /// </comment>
InstantiateAndExecuteTaskInSTAThread( IBuildEngine oopTaskHostNode, LoadedType taskType, string taskName, string taskLocation, string taskFile, int taskLine, int taskColumn, AppDomainSetup appDomainSetup, IDictionary<string, TaskParameter> taskParams )215         private OutOfProcTaskHostTaskResult InstantiateAndExecuteTaskInSTAThread
216             (
217                 IBuildEngine oopTaskHostNode,
218                 LoadedType taskType,
219                 string taskName,
220                 string taskLocation,
221                 string taskFile,
222                 int taskLine,
223                 int taskColumn,
224 #if FEATURE_APPDOMAIN
225                 AppDomainSetup appDomainSetup,
226 #endif
227                 IDictionary<string, TaskParameter> taskParams
228             )
229         {
230             ManualResetEvent taskRunnerFinished = new ManualResetEvent(false);
231             OutOfProcTaskHostTaskResult taskResult = null;
232             Exception exceptionFromExecution = null;
233 
234             try
235             {
236                 ThreadStart taskRunnerDelegate = delegate ()
237                 {
238                     try
239                     {
240                         taskResult = InstantiateAndExecuteTask
241                                             (
242                                                 oopTaskHostNode,
243                                                 taskType,
244                                                 taskName,
245                                                 taskLocation,
246                                                 taskFile,
247                                                 taskLine,
248                                                 taskColumn,
249 #if FEATURE_APPDOMAIN
250                                                 appDomainSetup,
251 #endif
252                                                 taskParams
253                                             );
254                     }
255                     catch (Exception e)
256                     {
257                         if (ExceptionHandling.IsCriticalException(e))
258                         {
259                             throw;
260                         }
261 
262                         exceptionFromExecution = e;
263                     }
264                     finally
265                     {
266                         taskRunnerFinished.Set();
267                     }
268                 };
269 
270                 Thread staThread = new Thread(taskRunnerDelegate);
271                 staThread.SetApartmentState(ApartmentState.STA);
272                 staThread.Name = "MSBuild STA task runner thread";
273                 staThread.CurrentCulture = Thread.CurrentThread.CurrentCulture;
274                 staThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
275                 staThread.Start();
276 
277                 // TODO: Why not just Join on the thread???
278                 taskRunnerFinished.WaitOne();
279             }
280             finally
281             {
282 #if CLR2COMPATIBILITY
283                 taskRunnerFinished.Close();
284 #else
285                 taskRunnerFinished.Dispose();
286 #endif
287                 taskRunnerFinished = null;
288             }
289 
290             if (exceptionFromExecution != null)
291             {
292                 // Unfortunately this will reset the callstack
293                 throw exceptionFromExecution;
294             }
295 
296             return taskResult;
297         }
298 #endif
299 
300         /// <summary>
301         /// Do the work of actually instantiating and running the task.
302         /// </summary>
InstantiateAndExecuteTask( IBuildEngine oopTaskHostNode, LoadedType taskType, string taskName, string taskLocation, string taskFile, int taskLine, int taskColumn, AppDomainSetup appDomainSetup, IDictionary<string, TaskParameter> taskParams )303         private OutOfProcTaskHostTaskResult InstantiateAndExecuteTask
304             (
305                 IBuildEngine oopTaskHostNode,
306                 LoadedType taskType,
307                 string taskName,
308                 string taskLocation,
309                 string taskFile,
310                 int taskLine,
311                 int taskColumn,
312 #if FEATURE_APPDOMAIN
313                 AppDomainSetup appDomainSetup,
314 #endif
315                 IDictionary<string, TaskParameter> taskParams
316             )
317         {
318 #if FEATURE_APPDOMAIN
319             _taskAppDomain = null;
320 #endif
321             wrappedTask = null;
322 
323             try
324             {
325                 wrappedTask = TaskLoader.CreateTask(taskType, taskName, taskFile, taskLine, taskColumn, new TaskLoader.LogError(LogErrorDelegate),
326 #if FEATURE_APPDOMAIN
327                     appDomainSetup,
328 #endif
329                     true /* always out of proc */
330 #if FEATURE_APPDOMAIN
331                     , out _taskAppDomain
332 #endif
333                     );
334                 Type wrappedTaskType = wrappedTask.GetType();
335 
336                 wrappedTask.BuildEngine = oopTaskHostNode;
337             }
338             catch (Exception e)
339             {
340                 if (ExceptionHandling.IsCriticalException(e))
341                 {
342                     throw;
343                 }
344 
345                 Exception exceptionToReturn = e;
346 
347                 // If it's a TargetInvocationException, we only care about the contents of the inner exception,
348                 // so just save that instead.
349                 if (e is TargetInvocationException)
350                 {
351                     exceptionToReturn = e.InnerException;
352                 }
353 
354                 return new OutOfProcTaskHostTaskResult
355                 (
356                     TaskCompleteType.CrashedDuringInitialization,
357                     exceptionToReturn,
358                     "TaskInstantiationFailureError",
359                     new string[] { taskName, taskLocation, String.Empty }
360                 );
361             }
362 
363             foreach (KeyValuePair<string, TaskParameter> param in taskParams)
364             {
365                 try
366                 {
367                     PropertyInfo paramInfo = wrappedTask.GetType().GetProperty(param.Key, BindingFlags.Instance | BindingFlags.Public);
368                     paramInfo.SetValue(wrappedTask, (param.Value == null ? null : param.Value.WrappedParameter), null);
369                 }
370                 catch (Exception e)
371                 {
372                     if (ExceptionHandling.IsCriticalException(e))
373                     {
374                         throw;
375                     }
376 
377                     Exception exceptionToReturn = e;
378 
379                     // If it's a TargetInvocationException, we only care about the contents of the inner exception,
380                     // so just save that instead.
381                     if (e is TargetInvocationException)
382                     {
383                         exceptionToReturn = e.InnerException;
384                     }
385 
386                     return new OutOfProcTaskHostTaskResult
387                                     (
388                                         TaskCompleteType.CrashedDuringInitialization,
389                                         exceptionToReturn,
390                                         "InvalidTaskAttributeError",
391                                         new string[] { param.Key, param.Value.ToString(), taskName }
392                                     );
393                 }
394             }
395 
396             bool success = false;
397             try
398             {
399                 if (CancelPending)
400                 {
401                     return new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure);
402                 }
403 
404                 // If it didn't crash and return before now, we're clear to go ahead and execute here.
405                 success = wrappedTask.Execute();
406             }
407             catch (Exception e)
408             {
409                 if (ExceptionHandling.IsCriticalException(e))
410                 {
411                     throw;
412                 }
413 
414                 return new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, e);
415             }
416 
417             PropertyInfo[] finalPropertyValues = wrappedTask.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
418 
419             IDictionary<string, Object> finalParameterValues = new Dictionary<string, Object>(StringComparer.OrdinalIgnoreCase);
420             foreach (PropertyInfo value in finalPropertyValues)
421             {
422                 // only record outputs
423                 if (value.GetCustomAttributes(typeof(OutputAttribute), true).Count() > 0)
424                 {
425                     try
426                     {
427                         finalParameterValues[value.Name] = value.GetValue(wrappedTask, null);
428                     }
429                     catch (Exception e)
430                     {
431                         if (ExceptionHandling.IsCriticalException(e))
432                         {
433                             throw;
434                         }
435 
436                         // If it's not a critical exception, we assume there's some sort of problem in the parameter getter --
437                         // so save the exception, and we'll re-throw once we're back on the main node side of the
438                         // communications pipe.
439                         finalParameterValues[value.Name] = e;
440                     }
441                 }
442             }
443 
444             return new OutOfProcTaskHostTaskResult(success ? TaskCompleteType.Success : TaskCompleteType.Failure, finalParameterValues);
445         }
446 
447         /// <summary>
448         /// Logs errors from TaskLoader
449         /// </summary>
LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs)450         private void LogErrorDelegate(string taskLocation, int taskLine, int taskColumn, string message, params object[] messageArgs)
451         {
452             buildEngine.LogErrorEvent(new BuildErrorEventArgs(
453                                                     null,
454                                                     null,
455                                                     taskLocation,
456                                                     taskLine,
457                                                     taskColumn,
458                                                     0,
459                                                     0,
460                                                     ResourceUtilities.FormatString(AssemblyResources.GetString(message), messageArgs),
461                                                     null,
462                                                     taskName
463                                                 )
464                                             );
465         }
466     }
467 }
468