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 using System; 5 using System.Collections; 6 using System.Collections.Generic; 7 using System.Security.Permissions; 8 using System.Xml; 9 using Microsoft.Build.Framework; 10 using Microsoft.Build.BuildEngine.Shared; 11 using System.Globalization; 12 using System.Threading; 13 using System.Runtime.Remoting.Lifetime; 14 using System.Runtime.Remoting; 15 16 namespace Microsoft.Build.BuildEngine 17 { 18 /// <summary> 19 /// This class serves as a surrogate for the build engine. It limits access to the build engine by implementing only a subset 20 /// of all public methods on the Engine class. 21 /// </summary> 22 internal sealed class EngineProxy : MarshalByRefObject, IBuildEngine3 23 { 24 #region Data 25 // The logging interface 26 private EngineLoggingServices loggingServices; 27 28 // We've already computed and cached the line/column number of the task node in the project file. 29 private bool haveProjectFileLocation = false; 30 31 // The line number of the task node in the calling project file. 32 private int lineNumber; 33 34 // The column number of the task node in the calling project file. 35 private int columnNumber; 36 37 /// <summary> 38 /// The full path to the project that's currently building. 39 /// </summary> 40 private string parentProjectFullFileName; 41 42 /// <summary> 43 /// The project file that contains the XML for task. This may be an import file and not the primary 44 /// project file 45 /// </summary> 46 private string projectFileOfTaskNode; 47 48 /// <summary> 49 /// The token identifing the context of this evaluation 50 /// </summary> 51 private int handleId; 52 53 /// <summary> 54 /// Continue on error value per batch exposed via IBuildEngine 55 /// </summary> 56 private bool continueOnError; 57 58 /// <summary> 59 /// The module within which this class has been created. Used for all callbacks to 60 /// engine. 61 /// </summary> 62 private TaskExecutionModule parentModule; 63 64 /// <summary> 65 /// Event contextual information, this tells the loggers where the task events were fired from 66 /// </summary> 67 private BuildEventContext buildEventContext; 68 69 /// <summary> 70 /// True if the task connected to this proxy is alive 71 /// </summary> 72 private bool activeProxy; 73 74 /// <summary> 75 /// This reference type is used to block access to a single entry methods of the interface 76 /// </summary> 77 private object callbackMonitor; 78 79 /// <summary> 80 /// A client sponsor is a class 81 /// which will respond to a lease renewal request and will 82 /// increase the lease time allowing the object to stay in memory 83 /// </summary> 84 private ClientSponsor sponsor; 85 86 /// <summary> 87 /// Will hold cached copy of typeof(BuildErrorEventArgs) used by each call to LogError 88 /// </summary> 89 private static Type buildErrorEventArgsType = null; 90 91 /// <summary> 92 /// Will hold cached copy of typeof(BuildErrorEventArgs) used by each call to LogError 93 /// </summary> 94 private static Type buildWarningEventArgsType = null; 95 96 #endregion 97 98 /// <summary> 99 /// Private default constructor disallows parameterless instantiation. 100 /// </summary> EngineProxy()101 private EngineProxy() 102 { 103 // do nothing 104 } 105 106 /// <summary> 107 /// Create an instance of this class to represent the IBuildEngine2 interface to the task 108 /// including the event location where the log messages are raised 109 /// </summary> 110 /// <param name="parentModule">Parent Task Execution Module</param> 111 /// <param name="handleId"></param> 112 /// <param name="parentProjectFullFileName">the full path to the currently building project</param> 113 /// <param name="projectFileOfTaskNode">the path to the actual file (project or targets) where the task invocation is located</param> 114 /// <param name="loggingServices"></param> 115 /// <param name="buildEventContext">Event Context where events will be seen to be raised from. Task messages will get this as their event context</param> EngineProxy( TaskExecutionModule parentModule, int handleId, string parentProjectFullFileName, string projectFileOfTaskNode, EngineLoggingServices loggingServices, BuildEventContext buildEventContext )116 internal EngineProxy 117 ( 118 TaskExecutionModule parentModule, 119 int handleId, 120 string parentProjectFullFileName, 121 string projectFileOfTaskNode, 122 EngineLoggingServices loggingServices, 123 BuildEventContext buildEventContext 124 ) 125 { 126 ErrorUtilities.VerifyThrow(parentModule != null, "No parent module."); 127 ErrorUtilities.VerifyThrow(loggingServices != null, "No logging services."); 128 ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need project file path string"); 129 130 this.parentModule = parentModule; 131 this.handleId = handleId; 132 this.parentProjectFullFileName = parentProjectFullFileName; 133 this.projectFileOfTaskNode = projectFileOfTaskNode; 134 this.loggingServices = loggingServices; 135 this.buildEventContext = buildEventContext; 136 this.callbackMonitor = new object(); 137 138 activeProxy = true; 139 } 140 141 /// <summary> 142 /// Stub implementation -- forwards to engine being proxied. 143 /// </summary> LogErrorEvent(BuildErrorEventArgs e)144 public void LogErrorEvent(BuildErrorEventArgs e) 145 { 146 ErrorUtilities.VerifyThrowArgumentNull(e, "e"); 147 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 148 149 if (parentModule.IsRunningMultipleNodes && !e.GetType().IsSerializable) 150 { 151 loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(string.Empty), "ExpectedEventToBeSerializable", e.GetType().Name); 152 return; 153 } 154 155 string message = GetUpdatedMessage(e.File, e.Message, parentProjectFullFileName); 156 157 if (ContinueOnError) 158 { 159 // Convert the error into a warning. We do this because the whole point of 160 // ContinueOnError is that a project author expects that the task might fail, 161 // but wants to ignore the failures. This implies that we shouldn't be logging 162 // errors either, because you should never have a successful build with errors. 163 BuildWarningEventArgs warningEvent = new BuildWarningEventArgs 164 ( e.Subcategory, 165 e.Code, 166 e.File, 167 e.LineNumber, 168 e.ColumnNumber, 169 e.EndLineNumber, 170 e.EndColumnNumber, 171 message, // this is the new message from above 172 e.HelpKeyword, 173 e.SenderName); 174 175 warningEvent.BuildEventContext = buildEventContext; 176 loggingServices.LogWarningEvent(warningEvent); 177 178 // Log a message explaining why we converted the previous error into a warning. 179 loggingServices.LogComment(buildEventContext,MessageImportance.Normal, "ErrorConvertedIntoWarning"); 180 } 181 else 182 { 183 if(e.GetType().Equals(BuildErrorEventArgsType)) 184 { 185 // We'd like to add the project file to the subcategory, but since this property 186 // is read-only on the BuildErrorEventArgs type, this requires creating a new 187 // instance. However, if some task logged a custom error type, we don't want to 188 // impolitely (as we already do above on ContinueOnError) throw the custom type 189 // data away. 190 e = new BuildErrorEventArgs 191 ( 192 e.Subcategory, 193 e.Code, 194 e.File, 195 e.LineNumber, 196 e.ColumnNumber, 197 e.EndLineNumber, 198 e.EndColumnNumber, 199 message, // this is the new message from above 200 e.HelpKeyword, 201 e.SenderName 202 ); 203 } 204 205 e.BuildEventContext = buildEventContext; 206 loggingServices.LogErrorEvent(e); 207 } 208 } 209 210 /// <summary> 211 /// Stub implementation -- forwards to engine being proxied. 212 /// </summary> LogWarningEvent(BuildWarningEventArgs e)213 public void LogWarningEvent(BuildWarningEventArgs e) 214 { 215 ErrorUtilities.VerifyThrowArgumentNull(e, "e"); 216 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 217 218 if (parentModule.IsRunningMultipleNodes && !e.GetType().IsSerializable) 219 { 220 loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(string.Empty), "ExpectedEventToBeSerializable", e.GetType().Name); 221 return; 222 } 223 224 if (e.GetType().Equals(BuildWarningEventArgsType)) 225 { 226 // We'd like to add the project file to the message, but since this property 227 // is read-only on the BuildWarningEventArgs type, this requires creating a new 228 // instance. However, if some task logged a custom warning type, we don't want 229 // to impolitely throw the custom type data away. 230 231 string message = GetUpdatedMessage(e.File, e.Message, parentProjectFullFileName); 232 233 e = new BuildWarningEventArgs 234 ( 235 e.Subcategory, 236 e.Code, 237 e.File, 238 e.LineNumber, 239 e.ColumnNumber, 240 e.EndLineNumber, 241 e.EndColumnNumber, 242 message, // this is the new message from above 243 e.HelpKeyword, 244 e.SenderName 245 ); 246 } 247 248 e.BuildEventContext = buildEventContext; 249 loggingServices.LogWarningEvent(e); 250 } 251 252 /// <summary> 253 /// 254 /// </summary> 255 /// <param name="file">File field from the original BuildEventArgs</param> 256 /// <param name="message">Message field from the original BuildEventArgs</param> 257 /// <param name="parentProjectFullFileName">Full file name of the parent (building) project.</param> 258 /// <returns></returns> GetUpdatedMessage(string file, string message, string parentProjectFullFileName)259 private static string GetUpdatedMessage(string file, string message, string parentProjectFullFileName) 260 { 261 #if BUILDING_DF_LKG 262 // In the dogfood LKG, add the project path to the end, because we need it to help diagnose builds. 263 264 // Don't bother doing anything if we don't have a project path (e.g., we loaded from XML directly) 265 if (String.IsNullOrEmpty(parentProjectFullFileName)) 266 { 267 return message; 268 } 269 270 // Don't bother adding the project file path if it's already in the file part 271 if(String.Equals(file, parentProjectFullFileName, StringComparison.OrdinalIgnoreCase)) 272 { 273 return message; 274 } 275 276 string updatedMessage = String.IsNullOrEmpty(message) ? 277 String.Format(CultureInfo.InvariantCulture, "[{0}]", parentProjectFullFileName) : 278 String.Format(CultureInfo.InvariantCulture, "{0} [{1}]", message, parentProjectFullFileName); 279 280 return updatedMessage; 281 #else 282 // In the regular product, don't modify the message. We want to do this properly, with a field on the event args, in a future version. 283 return message; 284 #endif 285 } 286 287 /// <summary> 288 /// Stub implementation -- forwards to engine being proxied. 289 /// </summary> LogMessageEvent(BuildMessageEventArgs e)290 public void LogMessageEvent(BuildMessageEventArgs e) 291 { 292 ErrorUtilities.VerifyThrowArgumentNull(e, "e"); 293 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 294 295 if (parentModule.IsRunningMultipleNodes && !e.GetType().IsSerializable) 296 { 297 loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(string.Empty), "ExpectedEventToBeSerializable", e.GetType().Name); 298 return; 299 } 300 e.BuildEventContext = buildEventContext; 301 loggingServices.LogMessageEvent(e); 302 } 303 304 /// <summary> 305 /// Stub implementation -- forwards to engine being proxied. 306 /// </summary> LogCustomEvent(CustomBuildEventArgs e)307 public void LogCustomEvent(CustomBuildEventArgs e) 308 { 309 ErrorUtilities.VerifyThrowArgumentNull(e, "e"); 310 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 311 312 if (parentModule.IsRunningMultipleNodes && !e.GetType().IsSerializable) 313 { 314 loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(string.Empty), "ExpectedEventToBeSerializable", e.GetType().Name); 315 return; 316 } 317 318 e.BuildEventContext = buildEventContext; 319 loggingServices.LogCustomEvent(e); 320 } 321 322 /// <summary> 323 /// Returns true if the ContinueOnError flag was set to true for this particular task 324 /// in the project file. 325 /// </summary> 326 public bool ContinueOnError 327 { 328 get 329 { 330 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 331 332 return this.continueOnError; 333 } 334 } 335 336 /// <summary> 337 /// Called by the task engine to update the value for each batch 338 /// </summary> 339 /// <param name="shouldContinueOnError"></param> UpdateContinueOnError(bool shouldContinueOnError)340 internal void UpdateContinueOnError(bool shouldContinueOnError) 341 { 342 this.continueOnError = shouldContinueOnError; 343 } 344 345 /// <summary> 346 /// Retrieves the line number of the task node withing the project file that called it. 347 /// </summary> 348 /// <remarks>This method is expensive in terms of perf. Do not call it in mainline scenarios.</remarks> 349 /// <owner>RGoel</owner> 350 public int LineNumberOfTaskNode 351 { 352 get 353 { 354 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 355 356 ComputeProjectFileLocationOfTaskNode(); 357 return this.lineNumber; 358 } 359 } 360 361 /// <summary> 362 /// Retrieves the line number of the task node withing the project file that called it. 363 /// </summary> 364 /// <remarks>This method is expensive in terms of perf. Do not call it in mainline scenarios.</remarks> 365 /// <owner>RGoel</owner> 366 public int ColumnNumberOfTaskNode 367 { 368 get 369 { 370 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 371 372 ComputeProjectFileLocationOfTaskNode(); 373 return this.columnNumber; 374 } 375 } 376 377 /// <summary> 378 /// Returns the full path to the project file that contained the call to this task. 379 /// </summary> 380 public string ProjectFileOfTaskNode 381 { 382 get 383 { 384 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 385 386 return projectFileOfTaskNode; 387 } 388 } 389 390 /// <summary> 391 /// Computes the line/column number of the task node in the project file (or .TARGETS file) 392 /// that called it. 393 /// </summary> ComputeProjectFileLocationOfTaskNode()394 private void ComputeProjectFileLocationOfTaskNode() 395 { 396 if (!haveProjectFileLocation) 397 { 398 parentModule.GetLineColumnOfXmlNode(handleId, out this.lineNumber, out this.columnNumber); 399 haveProjectFileLocation = true; 400 } 401 } 402 403 /// <summary> 404 /// Stub implementation -- forwards to engine being proxied. 405 /// </summary> 406 /// <param name="projectFileName"></param> 407 /// <param name="targetNames"></param> 408 /// <param name="globalProperties"></param> 409 /// <param name="targetOutputs"></param> 410 /// <returns>result of call to engine</returns> BuildProjectFile( string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs )411 public bool BuildProjectFile 412 ( 413 string projectFileName, 414 string[] targetNames, 415 IDictionary globalProperties, 416 IDictionary targetOutputs 417 ) 418 { 419 return BuildProjectFile(projectFileName, targetNames, globalProperties, targetOutputs, null); 420 } 421 422 /// <summary> 423 /// Stub implementation -- forwards to engine being proxied. 424 /// </summary> 425 /// <param name="projectFileName"></param> 426 /// <param name="targetNames"></param> 427 /// <param name="globalProperties"></param> 428 /// <param name="targetOutputs"></param> 429 /// <param name="toolsVersion">Tools Version to override on the project. May be null</param> 430 /// <returns>result of call to engine</returns> BuildProjectFile( string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion )431 public bool BuildProjectFile 432 ( 433 string projectFileName, 434 string[] targetNames, 435 IDictionary globalProperties, 436 IDictionary targetOutputs, 437 string toolsVersion 438 ) 439 { 440 lock (callbackMonitor) 441 { 442 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 443 444 // Wrap the project name into an array 445 string[] projectFileNames = new string[1]; 446 projectFileNames[0] = projectFileName; 447 string[] toolsVersions = new string[1]; 448 toolsVersions[0] = toolsVersion; 449 IDictionary[] targetOutputsPerProject = new IDictionary[1]; 450 targetOutputsPerProject[0] = targetOutputs; 451 IDictionary[] globalPropertiesPerProject = new IDictionary[1]; 452 globalPropertiesPerProject[0] = globalProperties; 453 return parentModule.BuildProjectFile(handleId, projectFileNames, targetNames, globalPropertiesPerProject, targetOutputsPerProject, 454 loggingServices, toolsVersions, false, false, buildEventContext); 455 } 456 } 457 458 /// <summary> 459 /// Stub implementation -- forwards to engine being proxied. 460 /// </summary> 461 /// <param name="projectFileNames"></param> 462 /// <param name="targetNames"></param> 463 /// <param name="globalProperties"></param> 464 /// <param name="targetOutputsPerProject"></param> 465 /// <param name="toolsVersions">Tools Version to overrides per project. May contain null values</param> 466 /// <param name="unloadProjectsOnCompletion"></param> 467 /// <returns>result of call to engine</returns> BuildProjectFilesInParallel( string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersions, bool useResultsCache, bool unloadProjectsOnCompletion )468 public bool BuildProjectFilesInParallel 469 ( 470 string[] projectFileNames, 471 string[] targetNames, 472 IDictionary[] globalProperties, 473 IDictionary[] targetOutputsPerProject, 474 string[] toolsVersions, 475 bool useResultsCache, 476 bool unloadProjectsOnCompletion 477 ) 478 { 479 lock (callbackMonitor) 480 { 481 return parentModule.BuildProjectFile(handleId, projectFileNames, targetNames, globalProperties, 482 targetOutputsPerProject, loggingServices, 483 toolsVersions, useResultsCache, unloadProjectsOnCompletion, buildEventContext); 484 } 485 } 486 487 /// <summary> 488 /// Not implemented for the proxy 489 /// </summary> Yield()490 public void Yield() 491 { 492 } 493 494 /// <summary> 495 /// Not implemented for the proxy 496 /// </summary> Reacquire()497 public void Reacquire() 498 { 499 } 500 501 /// <summary> 502 /// Stub implementation -- forwards to engine being proxied. 503 /// </summary> 504 /// <remarks> 505 /// 1) it is acceptable to pass null for both <c>targetNames</c> and <c>targetOutputs</c> 506 /// 2) if no targets are specified, the default targets are built 507 /// 508 /// </remarks> 509 /// <param name="projectFileNames">The project to build.</param> 510 /// <param name="targetNames">The targets in the project to build (can be null).</param> 511 /// <param name="globalProperties">An array of hashtables of additional global properties to apply 512 /// to the child project (array entries can be null). 513 /// The key and value in the hashtable should both be strings.</param> 514 /// <param name="removeGlobalProperties">A list of global properties which should be removed.</param> 515 /// <param name="toolsVersions">A tools version recognized by the Engine that will be used during this build (can be null).</param> 516 /// <param name="returnTargetOutputs">Should the target outputs be returned in the BuildEngineResults</param> 517 /// <returns>Returns a structure containing the success or failures of the build and the target outputs by project.</returns> BuildProjectFilesInParallel( string[] projectFileNames, string[] targetNames, IDictionary [] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersions, bool returnTargetOutputs )518 public BuildEngineResult BuildProjectFilesInParallel 519 ( 520 string[] projectFileNames, 521 string[] targetNames, 522 IDictionary [] globalProperties, 523 IList<string>[] removeGlobalProperties, 524 string[] toolsVersions, 525 bool returnTargetOutputs 526 ) 527 { 528 lock (callbackMonitor) 529 { 530 ErrorUtilities.VerifyThrowInvalidOperation(activeProxy == true, "AttemptingToLogFromInactiveTask"); 531 532 ErrorUtilities.VerifyThrowArgumentNull(projectFileNames, "projectFileNames"); 533 ErrorUtilities.VerifyThrowArgumentNull(globalProperties, "globalPropertiesPerProject"); 534 535 Dictionary<string, ITaskItem[]>[] targetOutputsPerProject = null; 536 537 if (returnTargetOutputs) 538 { 539 targetOutputsPerProject = new Dictionary<string, ITaskItem[]>[projectFileNames.Length]; 540 for (int i = 0; i < targetOutputsPerProject.Length; i++) 541 { 542 targetOutputsPerProject[i] = new Dictionary<string, ITaskItem[]>(StringComparer.OrdinalIgnoreCase); 543 } 544 } 545 546 bool result = parentModule.BuildProjectFile(handleId, projectFileNames, targetNames, globalProperties, 547 targetOutputsPerProject, loggingServices, 548 toolsVersions, false, false, buildEventContext); 549 550 return new BuildEngineResult(result, new List<IDictionary<string, ITaskItem[]>>(targetOutputsPerProject)); 551 } 552 } 553 554 /// <summary> 555 /// InitializeLifetimeService is called when the remote object is activated. 556 /// This method will determine how long the lifetime for the object will be. 557 /// </summary> InitializeLifetimeService()558 public override object InitializeLifetimeService() 559 { 560 // Each MarshalByRef object has a reference to the service which 561 // controls how long the remote object will stay around 562 ILease lease = (ILease)base.InitializeLifetimeService(); 563 564 // Set how long a lease should be initially. Once a lease expires 565 // the remote object will be disconnected and it will be marked as being availiable 566 // for garbage collection 567 int initialLeaseTime = 1; 568 569 string initialLeaseTimeFromEnvironment = Environment.GetEnvironmentVariable("MSBUILDENGINEPROXYINITIALLEASETIME"); 570 571 if (!String.IsNullOrEmpty(initialLeaseTimeFromEnvironment)) 572 { 573 int leaseTimeFromEnvironment; 574 if (int.TryParse(initialLeaseTimeFromEnvironment , out leaseTimeFromEnvironment) && leaseTimeFromEnvironment > 0) 575 { 576 initialLeaseTime = leaseTimeFromEnvironment; 577 } 578 } 579 580 lease.InitialLeaseTime = TimeSpan.FromMinutes(initialLeaseTime); 581 582 // Make a new client sponsor. A client sponsor is a class 583 // which will respond to a lease renewal request and will 584 // increase the lease time allowing the object to stay in memory 585 sponsor = new ClientSponsor(); 586 587 // When a new lease is requested lets make it last 1 minutes longer. 588 int leaseExtensionTime = 1; 589 590 string leaseExtensionTimeFromEnvironment = Environment.GetEnvironmentVariable("MSBUILDENGINEPROXYLEASEEXTENSIONTIME"); 591 if (!String.IsNullOrEmpty(leaseExtensionTimeFromEnvironment)) 592 { 593 int leaseExtensionFromEnvironment; 594 if (int.TryParse(leaseExtensionTimeFromEnvironment , out leaseExtensionFromEnvironment) && leaseExtensionFromEnvironment > 0) 595 { 596 leaseExtensionTime = leaseExtensionFromEnvironment; 597 } 598 } 599 600 sponsor.RenewalTime = TimeSpan.FromMinutes(leaseExtensionTime); 601 602 // Register the sponsor which will increase lease timeouts when the lease expires 603 lease.Register(sponsor); 604 605 return lease; 606 } 607 608 609 /// <summary> 610 /// Indicates to the EngineProxy that it is no longer needed. 611 /// Called by TaskEngine when the task using the EngineProxy is done. 612 /// </summary> MarkAsInActive()613 internal void MarkAsInActive() 614 { 615 activeProxy = false; 616 617 // Since the task has a pointer to this class it may store it in a static field. Null out 618 // internal data so the leak of this object doesn't lead to a major memory leak. 619 loggingServices = null; 620 parentModule = null; 621 buildEventContext = null; 622 623 // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) 624 // this will be null if the engineproxy was never sent accross an appdomain boundry. 625 if (sponsor != null) 626 { 627 ILease lease = (ILease)RemotingServices.GetLifetimeService(this); 628 629 if (lease != null) 630 { 631 lease.Unregister(sponsor); 632 } 633 634 sponsor.Close(); 635 sponsor = null; 636 } 637 } 638 639 #region Properties 640 /// <summary> 641 /// Provide a way to change the BuildEventContext of the engine proxy. This is important in batching where each batch will need its own buildEventContext. 642 /// </summary> 643 internal BuildEventContext BuildEventContext 644 { 645 get { return buildEventContext; } 646 set { buildEventContext = value; } 647 } 648 649 /// <summary> 650 /// This property allows a task to query whether or not the system is running in single process mode or multi process mode. 651 /// Single process mode is where the engine is initialized with the number of cpus = 1 and the engine is not a child engine. 652 /// The engine is in multi process mode when the engine is initialized with a number of cpus > 1 or the engine is a child engine. 653 /// </summary> 654 public bool IsRunningMultipleNodes 655 { 656 get { return parentModule.IsRunningMultipleNodes; } 657 } 658 659 /// <summary> 660 /// Cached copy of typeof(BuildErrorEventArgs) used during each call to LogError 661 /// </summary> 662 private static Type BuildErrorEventArgsType 663 { 664 get 665 { 666 if (buildErrorEventArgsType == null) 667 { 668 buildErrorEventArgsType = typeof(BuildErrorEventArgs); 669 } 670 return buildErrorEventArgsType; 671 } 672 } 673 674 /// <summary> 675 /// Cached copy of typeof(BuildWarningEventArgs) used during each call to LogWarning 676 /// </summary> 677 private static Type BuildWarningEventArgsType 678 { 679 get 680 { 681 if (buildWarningEventArgsType == null) 682 { 683 buildWarningEventArgsType = typeof(BuildWarningEventArgs); 684 } 685 return buildWarningEventArgsType; 686 } 687 } 688 689 #endregion 690 } 691 } 692