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