/// <summary> /// Creates an instance of this class for the specified task. /// </summary> public TaskEngine ( XmlElement taskNodeXmlElement, ITaskHost hostObject, string projectFileOfTaskNode, string parentProjectFullFileName, EngineLoggingServices loggingServices, int handleId, TaskExecutionModule parentModule, BuildEventContext targetBuildEventContext ) { ErrorUtilities.VerifyThrow(taskNodeXmlElement != null, "Need to specify the task node."); ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need to specify path of project."); ErrorUtilities.VerifyThrow(parentProjectFullFileName != null, "Need to specify name of project."); ErrorUtilities.VerifyThrow(loggingServices != null, "Need to specify the node logger."); this.taskNode = taskNodeXmlElement; this.taskClass = null; this.hostObject = hostObject; this.projectFileOfTaskNode = projectFileOfTaskNode; this.parentProjectFullFileName = parentProjectFullFileName; this.loggingServices = loggingServices; this.handleId = handleId; this.parentModule = parentModule; this.continueOnError = false; this.conditionAttribute = taskNode.Attributes[XMakeAttributes.condition]; this.buildEventContext = targetBuildEventContext; }
/// <summary> /// Indicates to the EngineProxy that it is no longer needed. /// Called by TaskEngine when the task using the EngineProxy is done. /// </summary> internal void MarkAsInActive() { activeProxy = false; // Since the task has a pointer to this class it may store it in a static field. Null out // internal data so the leak of this object doesn't lead to a major memory leak. loggingServices = null; parentModule = null; buildEventContext = null; // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engineproxy was never sent accross an appdomain boundry. if (sponsor != null) { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); if (lease != null) { lease.Unregister(sponsor); } sponsor.Close(); sponsor = null; } }
/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// </summary> /// <param name="condition">Can be null</param> /// <param name="conditionAttribute">XML attribute on which the condition is evaluated</param> /// <param name="expander">All the data available for expanding embedded properties, metadata, and items</param> /// <param name="conditionedPropertiesTable">Can be null</param> /// <param name="itemListOptions"></param> /// <param name="loggingServices">Can be null</param> /// <param name="buildEventContext"> contains contextual information for logging events</param> /// <returns>true, if the expression evaluates to true, otherwise false</returns> internal static bool EvaluateCondition ( string condition, XmlAttribute conditionAttribute, Expander expander, Hashtable conditionedPropertiesTable, ParserOptions itemListOptions, EngineLoggingServices loggingServices, BuildEventContext buildEventContext ) { ErrorUtilities.VerifyThrow((conditionAttribute != null) || (condition.Length == 0), "If condition is non-empty, you must provide the XML node representing the condition."); // An empty condition is equivalent to a "true" condition. if ((null == condition) || (condition.Length == 0)) { return(true); } Hashtable cachedExpressionTreesForCurrentOptions = cachedExpressionTrees[(int)itemListOptions]; // Try and see if we have an expression tree for this condition already GenericExpressionNode parsedExpression = (GenericExpressionNode)cachedExpressionTreesForCurrentOptions[condition]; if (parsedExpression == null) { Parser conditionParser = new Parser(); #region REMOVE_COMPAT_WARNING conditionParser.LoggingServices = loggingServices; conditionParser.LogBuildEventContext = buildEventContext; #endregion parsedExpression = conditionParser.Parse(condition, conditionAttribute, itemListOptions); // It's possible two threads will add a different tree to the same entry in the hashtable, // but it should be rare and it's not a problem - the previous entry will be thrown away. // We could ensure no dupes with double check locking but it's not really necessary here. // Also, we don't want to lock on every read. lock (cachedExpressionTreesForCurrentOptions) { cachedExpressionTreesForCurrentOptions[condition] = parsedExpression; } } ConditionEvaluationState state = new ConditionEvaluationState(conditionAttribute, expander, conditionedPropertiesTable, condition); bool result; // We are evaluating this expression now and it can cache some state for the duration, // so we don't want multiple threads working on the same expression lock (parsedExpression) { result = parsedExpression.Evaluate(state); parsedExpression.ResetState(); } return(result); }
/// <summary> /// Given a task name and a list of assemblies, this helper method checks if the task exists in any of the assemblies. /// </summary> /// <remarks> /// If the task name is fully qualified, then a match (if any) is unambiguous; otherwise, if there are multiple tasks with /// the same name in different namespaces/assemblies, the first task found will be returned. /// </remarks> /// <param name="taskName"></param> /// <param name="taskAssemblies"></param> /// <param name="taskProjectFile"></param> /// <param name="taskNode"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> /// <param name="taskClass"></param> /// <returns>true, if task is successfully loaded</returns> private bool GetTaskFromAssembly ( string taskName, ArrayList taskAssemblies, string taskProjectFile, XmlNode taskNode, EngineLoggingServices loggingServices, BuildEventContext buildEventContext, out LoadedType taskClass ) { taskClass = null; foreach (AssemblyLoadInfo assembly in taskAssemblies) { try { taskClass = typeLoader.Load(taskName, assembly); } catch (TargetInvocationException e) { // Exception thrown by the called code itself // Log the stack, so the task vendor can fix their code ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskLoadFailure", taskName, assembly.ToString(), Environment.NewLine + e.InnerException.ToString()); } catch (ReflectionTypeLoadException e) { // ReflectionTypeLoadException.LoaderExceptions may contain nulls foreach (Exception exception in e.LoaderExceptions) { if (exception != null) { loggingServices.LogError(buildEventContext, new BuildEventFileInfo(taskProjectFile), "TaskLoadFailure", taskName, assembly.ToString(), exception.Message); } } ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskLoadFailure", taskName, assembly.ToString(), e.Message); } catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception. { if (ExceptionHandling.NotExpectedReflectionException(e)) { throw; } ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskLoadFailure", taskName, assembly.ToString(), e.Message); } if (taskClass != null) { return(true); } } return(false); }
internal TargetCycleDetector(EngineLoggingServices engineLoggingService, EngineCallback engineCallback) { this.engineLoggingService = engineLoggingService; this.engineCallback = engineCallback; dependencyGraph = new Hashtable(); outstandingExternalRequests = new Hashtable(); cycleParent = null; cycleChild = null; }
internal TargetCycleDetector(EngineLoggingServices engineLoggingService, EngineCallback engineCallback) { this.engineLoggingService = engineLoggingService; this.engineCallback = engineCallback; dependencyGraph = new Hashtable(); outstandingExternalRequests = new Hashtable(); cycleParent = null; cycleChild = null; }
/// <summary> /// Creates an instance of this class for the given target. /// </summary> /// <owner>SumedhK</owner> internal TargetDependencyAnalyzer(string projectDirectory, Target targetToAnalyze, EngineLoggingServices loggingServices, BuildEventContext buildEventContext) { ErrorUtilities.VerifyThrow(projectDirectory != null, "Need a project directory."); ErrorUtilities.VerifyThrow(targetToAnalyze != null, "Need a target to analyze."); ErrorUtilities.VerifyThrow(targetToAnalyze.TargetElement != null, "Need a target element."); this.projectDirectory = projectDirectory; this.targetToAnalyze = targetToAnalyze; this.targetInputsAttribute = targetToAnalyze.TargetElement.Attributes[XMakeAttributes.inputs]; this.targetOutputsAttribute = targetToAnalyze.TargetElement.Attributes[XMakeAttributes.outputs]; this.loggingService = loggingServices; this.buildEventContext = buildEventContext; }
/// <summary> /// Override to BuildProject file to return true so we can test that /// </summary> override internal bool BuildProjectFile ( int nodeProxyId, string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, EngineLoggingServices loggingServices, string[] toolsVersions, bool useResultsCache, bool unloadProjectsOnCompletion, BuildEventContext taskContext ) { return true; }
/// <summary> /// Evaluates a string representing a condition from a "condition" attribute. /// If the condition is a malformed string, it throws an InvalidProjectFileException. /// This method uses cached expression trees to avoid generating them from scratch every time it's called. /// This method is thread safe and is called from engine and task execution module threads /// </summary> /// <param name="condition">Can be null</param> /// <param name="conditionAttribute">XML attribute on which the condition is evaluated</param> /// <param name="expander">All the data available for expanding embedded properties, metadata, and items</param> /// <param name="itemListOptions"></param> /// <param name="loggingServices">Can be null</param> /// <param name="eventContext"> contains contextual information for logging events</param> /// <returns>true, if the expression evaluates to true, otherwise false</returns> internal static bool EvaluateCondition ( string condition, XmlAttribute conditionAttribute, Expander expander, ParserOptions itemListOptions, EngineLoggingServices loggingServices, BuildEventContext buildEventContext ) { return(EvaluateCondition(condition, conditionAttribute, expander, null, itemListOptions, loggingServices, buildEventContext)); }
/// <summary> /// Creates an IntrinsicTask object around a "task" node /// </summary> internal IntrinsicTask(XmlElement taskNodeXmlElement, EngineLoggingServices loggingServices, BuildEventContext eventContext, string executionDirectory, ItemDefinitionLibrary itemDefinitionLibrary) { this.taskNodeXmlElement = taskNodeXmlElement; this.conditionAttribute = taskNodeXmlElement.Attributes[XMakeAttributes.condition]; this.loggingServices = loggingServices; this.buildEventContext = eventContext; this.executionDirectory = executionDirectory; this.itemDefinitionLibrary = itemDefinitionLibrary; ErrorUtilities.VerifyThrow(IsIntrinsicTaskName(taskNodeXmlElement.Name), "Only PropertyGroup and ItemGroup are known intrinsic tasks"); switch (taskNodeXmlElement.Name) { case XMakeElements.propertyGroup: backingType = BackingType.PropertyGroup; // If the backing type is a property group, we can just use a property group object; its semantics aren't // tangled up with the project object. Put another way, we only really need the code that understands the XML // format of a property group, and we can get that without the rest of BuildPropertyGroup getting in the way. // Specify that these properties are output properties, so they get reverted when the project is reset. backingPropertyGroup = new BuildPropertyGroup(null /* no parent project */, taskNodeXmlElement, PropertyType.OutputProperty); break; case XMakeElements.itemGroup: backingType = BackingType.ItemGroup; // If the backing type is an item group, we just re-use the code that understands the XML format of an item group; // the semantics of BuildItemGroup are too coupled to its current use in the Project object for us to re-use it. backingItemGroupXml = new BuildItemGroupXml(taskNodeXmlElement); List <XmlElement> children = backingItemGroupXml.GetChildren(); backingBuildItemGroupChildren = new List <BuildItemGroupChildXml>(children.Count); foreach (XmlElement child in children) { BuildItemGroupChildXml childXml = new BuildItemGroupChildXml(child, ChildType.Any); backingBuildItemGroupChildren.Add(childXml); } break; } }
/// <summary> /// Creates an IntrinsicTask object around a "task" node /// </summary> internal IntrinsicTask(XmlElement taskNodeXmlElement, EngineLoggingServices loggingServices, BuildEventContext eventContext, string executionDirectory, ItemDefinitionLibrary itemDefinitionLibrary) { this.taskNodeXmlElement = taskNodeXmlElement; this.conditionAttribute = taskNodeXmlElement.Attributes[XMakeAttributes.condition]; this.loggingServices = loggingServices; this.buildEventContext = eventContext; this.executionDirectory = executionDirectory; this.itemDefinitionLibrary = itemDefinitionLibrary; ErrorUtilities.VerifyThrow(IsIntrinsicTaskName(taskNodeXmlElement.Name), "Only PropertyGroup and ItemGroup are known intrinsic tasks"); switch (taskNodeXmlElement.Name) { case XMakeElements.propertyGroup: backingType = BackingType.PropertyGroup; // If the backing type is a property group, we can just use a property group object; its semantics aren't // tangled up with the project object. Put another way, we only really need the code that understands the XML // format of a property group, and we can get that without the rest of BuildPropertyGroup getting in the way. // Specify that these properties are output properties, so they get reverted when the project is reset. backingPropertyGroup = new BuildPropertyGroup(null /* no parent project */, taskNodeXmlElement, PropertyType.OutputProperty); break; case XMakeElements.itemGroup: backingType = BackingType.ItemGroup; // If the backing type is an item group, we just re-use the code that understands the XML format of an item group; // the semantics of BuildItemGroup are too coupled to its current use in the Project object for us to re-use it. backingItemGroupXml = new BuildItemGroupXml(taskNodeXmlElement); List<XmlElement> children = backingItemGroupXml.GetChildren(); backingBuildItemGroupChildren = new List<BuildItemGroupChildXml>(children.Count); foreach (XmlElement child in children) { BuildItemGroupChildXml childXml = new BuildItemGroupChildXml(child, ChildType.Any); backingBuildItemGroupChildren.Add(childXml); } break; } }
/// <summary> /// Create an instance of this class to represent the IBuildEngine2 interface to the task /// including the event location where the log messages are raised /// </summary> /// <param name="parentModule">Parent Task Execution Module</param> /// <param name="handleId"></param> /// <param name="parentProjectFullFileName">the full path to the currently building project</param> /// <param name="projectFileOfTaskNode">the path to the actual file (project or targets) where the task invocation is located</param> /// <param name="loggingServices"></param> /// <param name="buildEventContext">Event Context where events will be seen to be raised from. Task messages will get this as their event context</param> internal EngineProxy ( TaskExecutionModule parentModule, int handleId, string parentProjectFullFileName, string projectFileOfTaskNode, EngineLoggingServices loggingServices, BuildEventContext buildEventContext ) { ErrorUtilities.VerifyThrow(parentModule != null, "No parent module."); ErrorUtilities.VerifyThrow(loggingServices != null, "No logging services."); ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need project file path string"); this.parentModule = parentModule; this.handleId = handleId; this.parentProjectFullFileName = parentProjectFullFileName; this.projectFileOfTaskNode = projectFileOfTaskNode; this.loggingServices = loggingServices; this.buildEventContext = buildEventContext; this.callbackMonitor = new object(); activeProxy = true; }
/// <summary> /// Initalize this class with a central logger id identifying the central logger to which /// these events should be forwarded and a logging service that will do the forwarding /// </summary> /// <param name="loggerId">central logger id</param> /// <param name="loggingService">engine logging service</param> internal EventRedirector(int loggerId, EngineLoggingServices loggingService) { this.loggerId = loggerId; this.loggingService = loggingService; }
/// <summary> /// Indicates to the EngineProxy that it is no longer needed. /// Called by TaskEngine when the task using the EngineProxy is done. /// </summary> internal void MarkAsInActive() { activeProxy = false; // Since the task has a pointer to this class it may store it in a static field. Null out // internal data so the leak of this object doesn't lead to a major memory leak. loggingServices = null; parentModule = null; buildEventContext = null; // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engineproxy was never sent accross an appdomain boundry. if (sponsor != null) { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); if (lease != null) { lease.Unregister(sponsor); } sponsor.Close(); sponsor = null; } }
/// <summary> /// Given a task name, this method retrieves the task class. If the task has been requested before, it will be found in /// the class cache; otherwise, <UsingTask> declarations will be used to search the appropriate assemblies. /// </summary> /// <param name="taskName"></param> /// <param name="taskProjectFile"></param> /// <param name="taskNode"></param> /// <param name="exactMatchRequired"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> /// <param name="taskClass"></param> /// <returns>true, if task is found</returns> public bool GetRegisteredTask ( string taskName, string taskProjectFile, XmlNode taskNode, bool exactMatchRequired, EngineLoggingServices loggingServices, BuildEventContext buildEventContext, out LoadedType taskClass ) { taskClass = null; // If there are no using tags in the project don't bother caching or looking for tasks if (registeredTasks == null) { return(false); } Hashtable cachedTaskClasses = exactMatchRequired ? this.cachedTaskClassesWithExactMatch : this.cachedTaskClassesWithFuzzyMatch; if (cachedTaskClasses.Contains(taskName)) { // Caller has asked us before for this same task name, and for the same value of "bool exactMatchRequired". // Return whatever the previous result was, even if it was null. Why would the result be different than // it was before? NOTE: Hash tables CAN have "null" as their value, and this still returns "true" for Contains(...). taskClass = (LoadedType)cachedTaskClasses[taskName]; } else { Hashtable registeredTasksFound; // look for the given task name in the registry; if not found, gather all registered task names that partially // match the given name if (FindRegisteredTasks(taskName, exactMatchRequired, out registeredTasksFound)) { foreach (DictionaryEntry registeredTaskFound in registeredTasksFound) { string mostSpecificTaskName = (string)registeredTaskFound.Key; // if the given task name is longer than the registered task name if (taskName.Length > ((string)registeredTaskFound.Key).Length) { // we will use the longer name to help disambiguate between multiple matches mostSpecificTaskName = taskName; } if (GetTaskFromAssembly(mostSpecificTaskName, (ArrayList)registeredTaskFound.Value, taskProjectFile, taskNode, loggingServices, buildEventContext, out taskClass)) { // Whilst we are within the processing of the task, we haven't actually started executing it, so // our using task message needs to be in the context of the target. However any errors should be reported // at the point where the task appears in the project. BuildEventContext usingTaskContext = new BuildEventContext(buildEventContext.NodeId, buildEventContext.TargetId, buildEventContext.ProjectContextId, BuildEventContext.InvalidTaskId); loggingServices.LogComment(usingTaskContext, "TaskFound", taskName, taskClass.Assembly.ToString()); break; } } } // Cache the result, even if it is null. We should never again do the work we just did, for this task name. cachedTaskClasses[taskName] = taskClass; } return(taskClass != null); }
/// <summary> /// Given a task name, this method retrieves the task class. If the task has been requested before, it will be found in /// the class cache; otherwise, <UsingTask> declarations will be used to search the appropriate assemblies. /// </summary> /// <param name="taskName"></param> /// <param name="taskProjectFile"></param> /// <param name="taskNode"></param> /// <param name="exactMatchRequired"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> /// <param name="taskClass"></param> /// <returns>true, if task is found</returns> public bool GetRegisteredTask ( string taskName, string taskProjectFile, XmlNode taskNode, bool exactMatchRequired, EngineLoggingServices loggingServices, BuildEventContext buildEventContext, out LoadedType taskClass ) { taskClass = null; // If there are no using tags in the project don't bother caching or looking for tasks if (registeredTasks == null) { return false; } Hashtable cachedTaskClasses = exactMatchRequired ? this.cachedTaskClassesWithExactMatch : this.cachedTaskClassesWithFuzzyMatch; if (cachedTaskClasses.Contains(taskName)) { // Caller has asked us before for this same task name, and for the same value of "bool exactMatchRequired". // Return whatever the previous result was, even if it was null. Why would the result be different than // it was before? NOTE: Hash tables CAN have "null" as their value, and this still returns "true" for Contains(...). taskClass = (LoadedType) cachedTaskClasses[taskName]; } else { Hashtable registeredTasksFound; // look for the given task name in the registry; if not found, gather all registered task names that partially // match the given name if (FindRegisteredTasks(taskName, exactMatchRequired, out registeredTasksFound)) { foreach (DictionaryEntry registeredTaskFound in registeredTasksFound) { string mostSpecificTaskName = (string)registeredTaskFound.Key; // if the given task name is longer than the registered task name if (taskName.Length > ((string)registeredTaskFound.Key).Length) { // we will use the longer name to help disambiguate between multiple matches mostSpecificTaskName = taskName; } if (GetTaskFromAssembly(mostSpecificTaskName, (ArrayList)registeredTaskFound.Value, taskProjectFile, taskNode, loggingServices, buildEventContext, out taskClass)) { // Whilst we are within the processing of the task, we haven't actually started executing it, so // our using task message needs to be in the context of the target. However any errors should be reported // at the point where the task appears in the project. BuildEventContext usingTaskContext = new BuildEventContext(buildEventContext.NodeId, buildEventContext.TargetId, buildEventContext.ProjectContextId, BuildEventContext.InvalidTaskId); loggingServices.LogComment(usingTaskContext, "TaskFound", taskName, taskClass.Assembly.ToString()); break; } } } // Cache the result, even if it is null. We should never again do the work we just did, for this task name. cachedTaskClasses[taskName] = taskClass; } return (taskClass != null); }
/// <summary> /// Reads the given <UsingTask> tag and saves the task information specified in it. /// </summary> /// <param name="usingTask"></param> /// <param name="expander"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> public void RegisterTask(UsingTask usingTask, Expander expander, EngineLoggingServices loggingServices, BuildEventContext buildEventContext) { if ( // if the <UsingTask> tag doesn't have a condition on it (usingTask.Condition == null) || // or if the condition holds Utilities.EvaluateCondition(usingTask.Condition, usingTask.ConditionAttribute, expander, null, ParserOptions.AllowProperties | ParserOptions.AllowItemLists, loggingServices, buildEventContext) ) { // Lazily allocate the hashtables if they are needed if (registeredTasks == null) { cachedTaskClassesWithExactMatch = new Hashtable(StringComparer.OrdinalIgnoreCase); cachedTaskClassesWithFuzzyMatch = new Hashtable(StringComparer.OrdinalIgnoreCase); registeredTasks = new Hashtable(StringComparer.OrdinalIgnoreCase); } string assemblyName = null; string assemblyFile = null; if (usingTask.AssemblyName != null) { // expand out all embedded properties and items in the assembly name assemblyName = expander.ExpandAllIntoString(usingTask.AssemblyName, usingTask.AssemblyNameAttribute); ProjectErrorUtilities.VerifyThrowInvalidProject(assemblyName.Length > 0, usingTask.AssemblyNameAttribute, "InvalidEvaluatedAttributeValue", assemblyName, usingTask.AssemblyName, XMakeAttributes.assemblyName, XMakeElements.usingTask); } else { // expand out all embedded properties and items in the assembly file/path assemblyFile = expander.ExpandAllIntoString(usingTask.AssemblyFile, usingTask.AssemblyFileAttribute); ProjectErrorUtilities.VerifyThrowInvalidProject(assemblyFile.Length > 0, usingTask.AssemblyFileAttribute, "InvalidEvaluatedAttributeValue", assemblyFile, usingTask.AssemblyFile, XMakeAttributes.assemblyFile, XMakeElements.usingTask); // figure out the directory of the project in which this <UsingTask> node was defined string projectFile = XmlUtilities.GetXmlNodeFile(usingTask.TaskNameAttribute.OwnerElement, String.Empty); string projectDir = (projectFile.Length > 0) ? Path.GetDirectoryName(projectFile) : String.Empty; // ensure the assembly file/path is relative to the project in which this <UsingTask> node was defined -- we // don't want paths from imported projects being interpreted relative to the main project file try { assemblyFile = Path.Combine(projectDir, assemblyFile); } catch (ArgumentException ex) { // Invalid chars in AssemblyFile path ProjectErrorUtilities.VerifyThrowInvalidProject(false, usingTask.AssemblyFileAttribute, "InvalidAttributeValueWithException", assemblyFile, XMakeAttributes.assemblyFile, XMakeElements.usingTask, ex.Message); } } AssemblyLoadInfo taskAssembly = new AssemblyLoadInfo(assemblyName, assemblyFile); // expand out all embedded properties and items string taskName = expander.ExpandAllIntoString(usingTask.TaskName, usingTask.TaskNameAttribute); ProjectErrorUtilities.VerifyThrowInvalidProject(taskName.Length > 0, usingTask.TaskNameAttribute, "InvalidEvaluatedAttributeValue", taskName, usingTask.TaskName, XMakeAttributes.taskName, XMakeElements.usingTask); // since more than one task can have the same name, we want to keep track of all assemblies that are declared to // contain tasks with a given name... ArrayList taskAssemblies = (ArrayList)registeredTasks[taskName]; if (taskAssemblies == null) { taskAssemblies = new ArrayList(); registeredTasks[taskName] = taskAssemblies; } taskAssemblies.Add(taskAssembly); } }
/// <summary> /// This function implements the callback via the IBuildEngine interface /// </summary> /// <returns>result of call to engine</returns> virtual internal bool BuildProjectFile ( int handleId, string[] projectFileNames, string[] targetNames, IDictionary[] globalPropertiesPerProject, IDictionary[] targetOutputsPerProject, EngineLoggingServices loggingServices, string [] toolsVersions, bool useResultsCache, bool unloadProjectsOnCompletion, BuildEventContext taskContext ) { if (projectFileNames.Length == 0) { // Nothing to do, just return success return(true); } string currentDir = FileUtilities.GetCurrentDirectoryStaticBuffer(currentDirectoryBuffer); if (Engine.debugMode) { string targetName = targetNames == null ? "null" : targetNames[0]; bool remoteNode = false; for (int r = 0; r < projectFileNames.Length; r++) { string fullProjectName = projectFileNames[r] ?? "null"; Console.WriteLine("RemoteNode: " + remoteNode + " Project " + fullProjectName + " T:" + targetName + " NodeProdyId# " + handleId + " Time " + DateTime.Now.ToLongTimeString()); if (globalPropertiesPerProject[r] != null) { foreach (DictionaryEntry entry in globalPropertiesPerProject[r]) { Console.WriteLine(currentDir + " :GLOBAL " + entry.Key + "=" + entry.Value.ToString()); } } } } BuildRequest[] buildRequests = new BuildRequest[projectFileNames.Length]; for (int i = 0; i < buildRequests.Length; i++) { // We need to get the full path to the project before we call back // into the engine which has no control over current path string fullProjectName = projectFileNames[i] != null? Path.GetFullPath(projectFileNames[i]) : null; buildRequests[i] = new BuildRequest(handleId, fullProjectName, targetNames, globalPropertiesPerProject[i], toolsVersions[i], i, useResultsCache, unloadProjectsOnCompletion); ErrorUtilities.VerifyThrow(buildRequests[i].IsGeneratedRequest, "Should not be sending non generated requests from TEM to engine"); buildRequests[i].ParentBuildEventContext = taskContext; } BuildResult[] buildResultsLocal = new BuildResult[projectFileNames.Length]; if (moduleMode == TaskExecutionModuleMode.SingleProcMode) { for (int i = 0; i < projectFileNames.Length; i++) { // If we are running in a single threaded mode we need to // re-enter the main build loop on the current thread in order // to build the requested project, because the main build is below // us on the stack engineCallback.PostBuildRequestsToHost(new BuildRequest[] { buildRequests[i] }); buildResultsLocal[i] = engineCallback.GetParentEngine().EngineBuildLoop(buildRequests[i]); buildResultsLocal[i].ConvertToTaskItems(); } } else { WaitForBuildResults(handleId, buildResultsLocal, buildRequests); } // Store the outputs in the hashtables provided by the caller bool overallResult = true; for (int i = 0; i < buildResultsLocal.Length; i++) { // Users of the Object Model can pass in null targetOutputs for projects they do not want outputs for // therefore we need to make sure that there are targetoutputs and the users want the results if (buildResultsLocal[i] != null) { if (buildResultsLocal[i].OutputsByTarget != null && targetOutputsPerProject[i] != null) { foreach (DictionaryEntry entry in buildResultsLocal[i].OutputsByTarget) { targetOutputsPerProject[i].Add(entry.Key, entry.Value); } overallResult = overallResult && buildResultsLocal[i].EvaluationResult; } } else { // The calculation was terminated prior to receiving the result overallResult = false; } } // We're now returning from an IBuildEngine callback; // set the current directory back to what the tasks expect if (Directory.GetCurrentDirectory() != currentDir) { Directory.SetCurrentDirectory(currentDir); } if (Engine.debugMode) { bool remoteNode = false; string targetName = targetNames == null ? "null" : targetNames[0]; Console.WriteLine("RemoteNode: " + remoteNode + " T:" + targetName + " HandleId# " + handleId + " Result " + overallResult); } return(overallResult); }
/// <summary> /// This function implements the callback via the IBuildEngine interface /// </summary> /// <returns>result of call to engine</returns> virtual internal bool BuildProjectFile ( int handleId, string[] projectFileNames, string[] targetNames, IDictionary[] globalPropertiesPerProject, IDictionary[] targetOutputsPerProject, EngineLoggingServices loggingServices, string [] toolsVersions, bool useResultsCache, bool unloadProjectsOnCompletion, BuildEventContext taskContext ) { if (projectFileNames.Length == 0) { // Nothing to do, just return success return true; } string currentDir = FileUtilities.GetCurrentDirectoryStaticBuffer(currentDirectoryBuffer); if (Engine.debugMode) { string targetName = targetNames == null ? "null" : targetNames[0]; bool remoteNode = false; for (int r = 0; r < projectFileNames.Length; r++) { string fullProjectName = projectFileNames[r] != null ? projectFileNames[r] : "null"; Console.WriteLine("RemoteNode: " + remoteNode + " Project " + fullProjectName + " T:" + targetName + " NodeProdyId# " + handleId + " Time " + DateTime.Now.ToLongTimeString()); if (globalPropertiesPerProject[r] != null) { foreach (DictionaryEntry entry in globalPropertiesPerProject[r]) { Console.WriteLine(currentDir + " :GLOBAL " + entry.Key + "=" + entry.Value.ToString()); } } } } BuildRequest[] buildRequests = new BuildRequest[projectFileNames.Length]; for (int i = 0; i < buildRequests.Length; i++) { // We need to get the full path to the project before we call back // into the engine which has no control over current path string fullProjectName = projectFileNames[i] != null ? Path.GetFullPath(projectFileNames[i]) : null; buildRequests[i] = new BuildRequest(handleId, fullProjectName, targetNames, globalPropertiesPerProject[i], toolsVersions[i], i, useResultsCache, unloadProjectsOnCompletion); ErrorUtilities.VerifyThrow(buildRequests[i].IsGeneratedRequest == true, "Should not be sending non generated requests from TEM to engine"); buildRequests[i].ParentBuildEventContext = taskContext; } BuildResult[] buildResultsLocal = new BuildResult[projectFileNames.Length]; if (moduleMode == TaskExecutionModuleMode.SingleProcMode) { for (int i = 0; i < projectFileNames.Length; i++) { // If we are running in a single threaded mode we need to // re-enter the main build loop on the current thread in order // to build the requested project, because the main build is below // us on the stack engineCallback.PostBuildRequestsToHost(new BuildRequest[] { buildRequests[i] }); buildResultsLocal[i] = engineCallback.GetParentEngine().EngineBuildLoop(buildRequests[i]); buildResultsLocal[i].ConvertToTaskItems(); } } else { WaitForBuildResults(handleId, buildResultsLocal, buildRequests); } // Store the outputs in the hashtables provided by the caller bool overallResult = true; for (int i = 0; i < buildResultsLocal.Length; i++) { // Users of the Object Model can pass in null targetOutputs for projects they do not want outputs for // therefore we need to make sure that there are targetoutputs and the users want the results if (buildResultsLocal[i] != null) { if (buildResultsLocal[i].OutputsByTarget != null && targetOutputsPerProject[i] != null) { foreach (DictionaryEntry entry in buildResultsLocal[i].OutputsByTarget) { targetOutputsPerProject[i].Add(entry.Key, entry.Value); } overallResult = overallResult && buildResultsLocal[i].EvaluationResult; } } else { // The calculation was terminated prior to receiving the result overallResult = false; } } // We're now returning from an IBuildEngine callback; // set the current directory back to what the tasks expect if (Directory.GetCurrentDirectory() != currentDir) { Directory.SetCurrentDirectory(currentDir); } if (Engine.debugMode) { bool remoteNode = false; string targetName = targetNames == null ? "null" : targetNames[0]; Console.WriteLine("RemoteNode: " + remoteNode + " T:" + targetName + " HandleId# " + handleId + " Result " + overallResult); } return overallResult; }
/// <summary> /// Constructor to init all data except for BinPath which is initialized separately because /// a parameterless constructor is needed for COM interop /// </summary> internal Engine ( int numberOfCpus, bool isChildNode, int parentNodeId, string localNodeProviderParameters, BuildPropertyGroup globalProperties, ToolsetDefinitionLocations locations ) { // No need to check whether locations parameter // is null, because it is a value type this.startupDirectory = Environment.CurrentDirectory; this.engineGlobalProperties = globalProperties == null ? new BuildPropertyGroup() : globalProperties; this.environmentProperties = new BuildPropertyGroup(); this.toolsetStateMap = new Dictionary<string, ToolsetState>(StringComparer.OrdinalIgnoreCase); this.toolsets = new ToolsetCollection(this); // Every environment variable can be referenced just like a property // from the project file. Here, we go ahead and add all the environment // variables to the property bag, so they can be treated just like any // other property later on. this.environmentProperties.GatherEnvironmentVariables(); this.projectsLoadedByHost = new Hashtable(StringComparer.OrdinalIgnoreCase); this.cacheOfBuildingProjects = new ProjectManager(); this.eventSource = new EventSource(); this.buildEnabled = true; this.flushRequestEvent = new ManualResetEvent(false); this.primaryLoggingServices = new EngineLoggingServicesInProc(eventSource, false, flushRequestEvent); // Read any toolsets from the registry and config file PopulateToolsetStateMap(locations); this.nodeId = parentNodeId; this.localNodeProviderParameters = localNodeProviderParameters; this.numberOfCpus = numberOfCpus; if (this.numberOfCpus == 1 && !isChildNode) { this.primaryLoggingServices.FlushBuildEventsImmediatly = true; } this.buildRequests = new DualQueue<BuildRequest>(); this.taskOutputUpdates = new DualQueue<TaskExecutionContext>(); this.engineCommands = new DualQueue<EngineCommand>(); this.engineCallback = new EngineCallback(this); this.nodeManager = new NodeManager(this.numberOfCpus, isChildNode, this); this.scheduler = new Scheduler(this.nodeId, this); this.router = new Router(this, scheduler); this.cacheManager = new CacheManager(this.DefaultToolsVersion); this.lastUsedLoggerId = EngineLoggingServicesInProc.FIRST_AVAILABLE_LOGGERID; this.enabledCentralLogging = false; this.introspector = new Introspector(this, cacheOfBuildingProjects, nodeManager); // Initialize the node provider InitializeLocalNodeProvider(locations); }
/// <summary> /// Initalize this class with a central logger id identifying the central logger to which /// these events should be forwarded and a logging service that will do the forwarding /// </summary> /// <param name="loggerId">central logger id</param> /// <param name="loggingService">engine logging service</param> internal EventRedirector(int loggerId, EngineLoggingServices loggingService) { this.loggerId = loggerId; this.loggingService = loggingService; }
/// <summary> /// This constructor initializes all required data. /// </summary> /// <owner>JomoF</owner> /// <param name="loggingServices"></param> /// <param name="binPath"></param> internal ProjectSchemaValidationHandler(BuildEventContext buildEventContext, EngineLoggingServices loggingServices, string binPath) { this.engineLoggingServices = loggingServices; this.binPath = binPath; this.buildEventContext = buildEventContext; }
/// <summary> /// This constructor initializes all required data. /// </summary> /// <owner>JomoF</owner> /// <param name="loggingServices"></param> /// <param name="binPath"></param> internal ProjectSchemaValidationHandler(BuildEventContext buildEventContext, EngineLoggingServices loggingServices, string binPath) { this.engineLoggingServices = loggingServices; this.binPath = binPath; this.buildEventContext = buildEventContext; }
/// <summary> /// Evaluates the item and returns a virtual group containing any resulting items. /// This allows an item to be evaluated without it belonging to an item group. /// </summary> internal BuildItemGroup Evaluate(Expander expander, string baseDirectory, bool expandMetadata, ParserOptions parserOptions, EngineLoggingServices loggingServices, BuildEventContext buildEventContext) { BuildItemGroup result = new BuildItemGroup(); bool itemCondition = Utilities.EvaluateCondition(Condition, ConditionAttribute, expander, parserOptions, loggingServices, buildEventContext); if (!itemCondition) { return result; } EvaluateAllItemMetadata(expander, parserOptions, loggingServices, buildEventContext); BuildItemGroup items = BuildItemGroup.ExpandItemIntoItems(baseDirectory, this, expander, expandMetadata); for (int i = 0; i < items.Count; i++) { BuildItem newItem = CreateClonedParentedItem(items[i], this); result.AddExistingItem(newItem); } return result; }
/// <summary> /// Reads the given <UsingTask> tag and saves the task information specified in it. /// </summary> /// <param name="usingTask"></param> /// <param name="expander"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> public void RegisterTask(UsingTask usingTask, Expander expander, EngineLoggingServices loggingServices, BuildEventContext buildEventContext) { if ( // if the <UsingTask> tag doesn't have a condition on it (usingTask.Condition == null) || // or if the condition holds Utilities.EvaluateCondition(usingTask.Condition, usingTask.ConditionAttribute, expander, null, ParserOptions.AllowProperties | ParserOptions.AllowItemLists, loggingServices, buildEventContext) ) { // Lazily allocate the hashtables if they are needed if (registeredTasks == null) { cachedTaskClassesWithExactMatch = new Hashtable(StringComparer.OrdinalIgnoreCase); cachedTaskClassesWithFuzzyMatch = new Hashtable(StringComparer.OrdinalIgnoreCase); registeredTasks = new Hashtable(StringComparer.OrdinalIgnoreCase); } string assemblyName = null; string assemblyFile = null; if (usingTask.AssemblyName != null) { // expand out all embedded properties and items in the assembly name assemblyName = expander.ExpandAllIntoString(usingTask.AssemblyName, usingTask.AssemblyNameAttribute); ProjectErrorUtilities.VerifyThrowInvalidProject(assemblyName.Length > 0, usingTask.AssemblyNameAttribute, "InvalidEvaluatedAttributeValue", assemblyName, usingTask.AssemblyName, XMakeAttributes.assemblyName, XMakeElements.usingTask); } else { // expand out all embedded properties and items in the assembly file/path assemblyFile = expander.ExpandAllIntoString(usingTask.AssemblyFile, usingTask.AssemblyFileAttribute); ProjectErrorUtilities.VerifyThrowInvalidProject(assemblyFile.Length > 0, usingTask.AssemblyFileAttribute, "InvalidEvaluatedAttributeValue", assemblyFile, usingTask.AssemblyFile, XMakeAttributes.assemblyFile, XMakeElements.usingTask); // figure out the directory of the project in which this <UsingTask> node was defined string projectFile = XmlUtilities.GetXmlNodeFile(usingTask.TaskNameAttribute.OwnerElement, String.Empty); string projectDir = (projectFile.Length > 0) ? Path.GetDirectoryName(projectFile) : String.Empty; // ensure the assembly file/path is relative to the project in which this <UsingTask> node was defined -- we // don't want paths from imported projects being interpreted relative to the main project file try { assemblyFile = Path.Combine(projectDir, assemblyFile); } catch (ArgumentException ex) { // Invalid chars in AssemblyFile path ProjectErrorUtilities.VerifyThrowInvalidProject(false, usingTask.AssemblyFileAttribute, "InvalidAttributeValueWithException", assemblyFile, XMakeAttributes.assemblyFile, XMakeElements.usingTask, ex.Message); } } AssemblyLoadInfo taskAssembly = new AssemblyLoadInfo(assemblyName, assemblyFile); // expand out all embedded properties and items string taskName = expander.ExpandAllIntoString(usingTask.TaskName, usingTask.TaskNameAttribute); ProjectErrorUtilities.VerifyThrowInvalidProject(taskName.Length > 0, usingTask.TaskNameAttribute, "InvalidEvaluatedAttributeValue", taskName, usingTask.TaskName, XMakeAttributes.taskName, XMakeElements.usingTask); // since more than one task can have the same name, we want to keep track of all assemblies that are declared to // contain tasks with a given name... ArrayList taskAssemblies = (ArrayList)registeredTasks[taskName]; if (taskAssemblies == null) { taskAssemblies = new ArrayList(); registeredTasks[taskName] = taskAssemblies; } taskAssemblies.Add(taskAssembly); } }
/// <summary> /// Populate the lists of evaluated and unevaluated metadata with all metadata that have true conditions. /// </summary> /// <remarks> /// FUTURE: Currently this isn't done when the item is constructed; so for example HasMetadata will always return /// false until EvaluatedAllItemMetadata is explicitly called. The reason for this is that Metadata are /// not first class objects, they are merely string pairs produced by running over the child XML with a particular expander. /// When Metadata are first class objects this method can be changed to merely evaluate them, /// just as BuildItemGroup.Evaluate does for BuildItem, then methods like HasMetadata behave more sanely. Of course this /// could be a breaking change. /// </remarks> internal void EvaluateAllItemMetadata ( Expander expander, ParserOptions parserOptions, EngineLoggingServices loggingServices, BuildEventContext buildEventContext ) { ErrorUtilities.VerifyThrow(expander != null, "Null expander passed in."); // Cache all custom attributes on the item. For a persisted item, this will walk the item's child nodes, and // cache the custom attributes using a "last one wins" policy. For a virtual item, this method does nothing. // We only evaluate metadata by reading XML if (IsBackedByXml) { ErrorUtilities.VerifyThrow((this.evaluatedCustomMetadata != null) && (this.unevaluatedCustomMetadata != null), "Item is not initialized properly."); // We're evaluating from scratch, so clear out any old cached attributes. this.evaluatedCustomMetadata.Clear(); this.unevaluatedCustomMetadata.Clear(); // Let the expander know about our own item type, so it can // expand unqualified metadata expressions SpecificItemDefinitionLibrary specificItemDefinitionLibrary = new SpecificItemDefinitionLibrary(name, itemDefinitionLibrary); expander = new Expander(expander, specificItemDefinitionLibrary); List<XmlElement> metadataElements = xml.GetChildren(); // look at all the item's child nodes foreach (XmlElement metadataElement in metadataElements) { // confirm that the child node is not conditionally disabled bool condition = true; XmlAttribute conditionAttribute = ProjectXmlUtilities.GetConditionAttribute(metadataElement, true /*no other attributes allowed*/); if (conditionAttribute != null) { condition = Utilities.EvaluateCondition(conditionAttribute.Value, conditionAttribute, expander, null, parserOptions, loggingServices, buildEventContext); } if (condition) { // cache its value, both the evaluated and unevaluated. string unevaluatedMetadataValue = Utilities.GetXmlNodeInnerContents(metadataElement); unevaluatedCustomMetadata[metadataElement.Name] = unevaluatedMetadataValue; string evaluatedMetadataValue = expander.ExpandAllIntoStringLeaveEscaped(unevaluatedMetadataValue, metadataElement); evaluatedCustomMetadata[metadataElement.Name] = evaluatedMetadataValue; // Add this metadata to the running table we're using, so that one piece of metadata can refer to another one above expander.SetMetadataInMetadataTable(name, metadataElement.Name, evaluatedMetadataValue); } } } }
/// <summary> /// Given a task name and a list of assemblies, this helper method checks if the task exists in any of the assemblies. /// </summary> /// <remarks> /// If the task name is fully qualified, then a match (if any) is unambiguous; otherwise, if there are multiple tasks with /// the same name in different namespaces/assemblies, the first task found will be returned. /// </remarks> /// <param name="taskName"></param> /// <param name="taskAssemblies"></param> /// <param name="taskProjectFile"></param> /// <param name="taskNode"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> /// <param name="taskClass"></param> /// <returns>true, if task is successfully loaded</returns> private bool GetTaskFromAssembly ( string taskName, ArrayList taskAssemblies, string taskProjectFile, XmlNode taskNode, EngineLoggingServices loggingServices, BuildEventContext buildEventContext, out LoadedType taskClass ) { taskClass = null; foreach (AssemblyLoadInfo assembly in taskAssemblies) { try { taskClass = typeLoader.Load(taskName, assembly); } catch (TargetInvocationException e) { // Exception thrown by the called code itself // Log the stack, so the task vendor can fix their code ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskLoadFailure", taskName, assembly.ToString(), Environment.NewLine + e.InnerException.ToString()); } catch (ReflectionTypeLoadException e) { // ReflectionTypeLoadException.LoaderExceptions may contain nulls foreach (Exception exception in e.LoaderExceptions) { if (exception != null) { loggingServices.LogError(buildEventContext, new BuildEventFileInfo(taskProjectFile), "TaskLoadFailure", taskName, assembly.ToString(), exception.Message); } } ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskLoadFailure", taskName, assembly.ToString(), e.Message); } catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception. { if (ExceptionHandling.NotExpectedReflectionException(e)) throw; ProjectErrorUtilities.VerifyThrowInvalidProject(false, taskNode, "TaskLoadFailure", taskName, assembly.ToString(), e.Message); } if (taskClass != null) { return true; } } return false; }
/// <summary> /// Create an instance of this class to represent the IBuildEngine2 interface to the task /// including the event location where the log messages are raised /// </summary> /// <param name="parentModule">Parent Task Execution Module</param> /// <param name="handleId"></param> /// <param name="parentProjectFullFileName">the full path to the currently building project</param> /// <param name="projectFileOfTaskNode">the path to the actual file (project or targets) where the task invocation is located</param> /// <param name="loggingServices"></param> /// <param name="buildEventContext">Event Context where events will be seen to be raised from. Task messages will get this as their event context</param> internal EngineProxy ( TaskExecutionModule parentModule, int handleId, string parentProjectFullFileName, string projectFileOfTaskNode, EngineLoggingServices loggingServices, BuildEventContext buildEventContext ) { ErrorUtilities.VerifyThrow(parentModule != null, "No parent module."); ErrorUtilities.VerifyThrow(loggingServices != null, "No logging services."); ErrorUtilities.VerifyThrow(projectFileOfTaskNode != null, "Need project file path string"); this.parentModule = parentModule; this.handleId = handleId; this.parentProjectFullFileName = parentProjectFullFileName; this.projectFileOfTaskNode = projectFileOfTaskNode; this.loggingServices = loggingServices; this.buildEventContext = buildEventContext; this.callbackMonitor = new object(); activeProxy = true; }