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