/// <summary> /// This method creates a new routing context. This method is not thread safe and must be called /// only from the engine thread. /// </summary> internal int CreateRoutingContext ( int nodeIndex, int parentHandleId, int parentNodeIndex, int parentRequestId, CacheScope cacheScope, BuildRequest triggeringBuildRequest, BuildEventContext buildEventContext ) { int handleId = nextContextId; nextContextId = nextContextId + 1; RequestRoutingContext executionContext = new RequestRoutingContext(handleId, nodeIndex, parentHandleId, parentNodeIndex, parentRequestId, cacheScope, triggeringBuildRequest, buildEventContext); executionContexts.Add(handleId, executionContext); return(handleId); }
/// <summary> /// This method get a result from the cache if every target is cached. /// If any of the target are not present in the cache null is returned. This method is not thread safe. /// </summary> internal BuildResult GetCachedBuildResult(BuildRequest buildRequest, out ArrayList actuallyBuiltTargets) { actuallyBuiltTargets = null; if (!buildRequest.UseResultsCache) { return(null); } // Retrieve list of scopes by this name string projectName = buildRequest.ProjectToBuild == null ? buildRequest.ProjectFileName : buildRequest.ProjectToBuild.FullFileName; // If the list exists search for matching scope properties otherwise create the list CacheScope cacheScope = GetCacheScopeIfExists(projectName, buildRequest.GlobalProperties, buildRequest.ToolsetVersion, CacheContentType.BuildResults); // If there is no cache entry for this project return null if (cacheScope == null) { return(null); } return(cacheScope.GetCachedBuildResult(buildRequest, out actuallyBuiltTargets)); }
/// <summary> /// Called when the engine is in the process of sending a buildRequest to a child node. The entire purpose of this method /// is to switch the traversal strategy of the systems if there are nodes which do not have enough work availiable to them. /// </summary> internal void NotifyOfBuildRequest(int nodeIndex, BuildRequest currentRequest, int parentHandleId) { // This will only be null when the scheduler is instantiated on a child process in which case the initialize method // of the scheduler will not be called and therefore not initialize totalRequestsPerNode. if (totalRequestsPerNode != null) { // Check if it makes sense to switch from one traversal strategy to the other if (parentEngine.NodeManager.TaskExecutionModule.UseBreadthFirstTraversal == true) { // Check if a switch to depth first traversal is in order bool useBreadthFirstTraversal = false; for (int i = 0; i < totalRequestsPerNode.Length; i++) { // Continue using breadth-first traversal as long as the non-blocked work load for this node is below // the nodeWorkloadProjectCount or its postBlockCount is non-zero if ((totalRequestsPerNode[i] - blockedRequestsPerNode[i]) < nodeWorkLoadProjectCount || postBlockCount[i] != 0) { useBreadthFirstTraversal = true; break; } } if (useBreadthFirstTraversal == false) { if (Engine.debugMode) { Console.WriteLine("Switching to depth first traversal because all node have workitems"); } parentEngine.NodeManager.TaskExecutionModule.UseBreadthFirstTraversal = false; // Switch to depth first and change the traversal strategy of the entire system by notifying all child nodes of the change parentEngine.PostEngineCommand(new ChangeTraversalTypeCommand(false, false)); } } } }
internal static BuildRequest CreateFromStream(BinaryReader reader) { BuildRequest request = new BuildRequest(); request.requestId = reader.ReadInt32(); request.handleId = reader.ReadInt32(); #region ProjectFileName if (reader.ReadByte() == 0) { request.projectFileName = null; } else { request.projectFileName = reader.ReadString(); } #endregion #region TargetNames if (reader.ReadByte() == 0) { request.targetNames = null; } else { int numberOfTargetNames = reader.ReadInt32(); request.targetNames = new string[numberOfTargetNames]; for (int i = 0; i < numberOfTargetNames; i++) { if (reader.ReadByte() == 0) { request.targetNames[i] = null; } else { request.targetNames[i] = reader.ReadString(); } } } #endregion #region GlobalProperties if (reader.ReadByte() == 0) { request.globalProperties = null; } else { request.globalProperties = new BuildPropertyGroup(); request.globalProperties.CreateFromStream(reader); } #endregion #region ToolsetVersion if (reader.ReadByte() == 0) { request.toolsetVersion = null; } else { request.toolsetVersion = reader.ReadString(); } #endregion request.unloadProjectsOnCompletion = reader.ReadBoolean(); request.useResultsCache = reader.ReadBoolean(); #region BuildEventContext if (reader.ReadByte() == 0) { request.buildEventContext = null; } else { // Re create event context int nodeId = reader.ReadInt32(); int projectContextId = reader.ReadInt32(); int targetId = reader.ReadInt32(); int taskId = reader.ReadInt32(); request.buildEventContext = new BuildEventContext(nodeId, targetId, projectContextId, taskId); } #endregion #region ToolsVersionPeekedFromProjectFile // We need to pass this over shared memory because where ever this project is being built needs to know // if the tools version was an override or was retreived from the project file if (reader.ReadByte() == 0) { request.toolsVersionPeekedFromProjectFile = false; } else { request.toolsVersionPeekedFromProjectFile = true; } #endregion return(request); }
private void NodeLocalEngineLoop() { buildInProgress = true; // Create a logging service for this build request localEngine = new Engine(parentGlobalProperties, toolsetSearchLocations, 1 /* cpus */, true /* child node */, this.nodeId, parentStartupDirectory, null); localEngine.Router.ChildMode = true; localEngine.Router.ParentNode = this; this.outProcLoggingService = new EngineLoggingServicesOutProc(this, localEngine.FlushRequestEvent); if (nodeLoggers.Length != 0) { foreach (LoggerDescription loggerDescription in nodeLoggers) { IForwardingLogger newLogger = null; bool exitedDueToError = true; try { newLogger = loggerDescription.CreateForwardingLogger(); // Check if the class was not found in the assembly if (newLogger == null) { InternalLoggerException.Throw(null, null, "FatalErrorWhileInitializingLogger", true, loggerDescription.Name); } newLogger.Verbosity = loggerDescription.Verbosity; newLogger.Parameters = loggerDescription.LoggerSwitchParameters; newLogger.NodeId = nodeId; EventRedirector newRedirector = new EventRedirector(loggerDescription.LoggerId, outProcLoggingService); newLogger.BuildEventRedirector = newRedirector; exitedDueToError = false; } // Polite logger failure catch (LoggerException e) { ReportUnhandledError(e); } // Logger class was not found catch (InternalLoggerException e) { ReportUnhandledError(e); } catch (Exception e) { // Wrap the exception in a InternalLoggerException and send it to the parent node string errorCode; string helpKeyword; string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, "FatalErrorWhileInitializingLogger", loggerDescription.Name); ReportUnhandledError(new InternalLoggerException(message, e, null, errorCode, helpKeyword, true)); } // If there was a failure registering loggers, null out the engine pointer if (exitedDueToError) { localEngine = null; return; } localEngine.RegisterLogger(newLogger); } localEngine.ExternalLoggingServices = outProcLoggingService; } // Hook up logging service to forward all events to the central engine if necessary if (centralizedLogging) { if (nodeLoggers.Length != 0) { localEngine.LoggingServices.ForwardingService = outProcLoggingService; localEngine.ExternalLoggingServices = outProcLoggingService; } else { localEngine.LoggingServices = outProcLoggingService; } } localEngine.LoggingServices.OnlyLogCriticalEvents = this.logOnlyCriticalEvents; if (!useBreadthFirstTraversal) { localEngine.PostEngineCommand(new ChangeTraversalTypeCommand(useBreadthFirstTraversal, true)); } // Post all the requests that passed in while the engine was being constructed // into the engine queue lock (buildRequests) { while (buildRequests.Count != 0) { BuildRequest buildRequest = buildRequests.Dequeue(); localEngine.PostBuildRequest(buildRequest); } } try { // If there are forwarding loggers registered - generate a custom build started if (nodeLoggers.Length > 0) { localEngine.LoggingServices.LogBuildStarted(EngineLoggingServicesInProc.CENTRAL_ENGINE_EVENTSOURCE); localEngine.LoggingServices.ProcessPostedLoggingEvents(); } // Trigger the actual build if shutdown was not called while the engine was being initialized if (!nodeShutdown) { localEngine.EngineBuildLoop(null); } } catch (Exception e) { // Unhandled exception during execution. The node has to be shutdown. ReportUnhandledError(e); } finally { if (localEngine != null) { // Flush all the messages associated before shutting down localEngine.LoggingServices.ProcessPostedLoggingEvents(); NodeManager nodeManager = localEngine.NodeManager; // If the local engine is already shutting down, the TEM will be nulled out if (nodeManager.TaskExecutionModule != null && nodeManager.TaskExecutionModule.TaskExecutionTime != 0) { TimeSpan taskTimeSpan = new TimeSpan(localEngine.NodeManager.TaskExecutionModule.TaskExecutionTime); totalTaskTime = (int)taskTimeSpan.TotalMilliseconds; } localEngine.Shutdown(); } // Flush all the events to the parent engine outProcLoggingService.ProcessPostedLoggingEvents(); // Indicate that the node logger thread should exit exitNodeEvent.Set(); } }
/// <summary> /// Post a build request to a node, the node index is an index into the list of nodes provided by all node providers /// registered to the node manager, the 0 in index is a local call to taskexecutionmodule /// </summary> /// <param name="nodeIndex"></param> /// <param name="buildRequest"></param> internal void PostBuildRequestToNode(int nodeIndex, BuildRequest buildRequest) { ErrorUtilities.VerifyThrow(nodeIndex != 0, "Should not use NodeManager to post to local TEM"); nodeList[nodeIndex - 1].NodeProvider.PostBuildRequestToNode(nodeList[nodeIndex - 1].NodeIndex, buildRequest); }
/// <summary> /// This function will start a node and send requests to it /// </summary> private void LaunchNodeAndPostBuildRequest() { int nodeIndex = 0; // Find out what node to launch lock (nodesToLaunch) { nodeIndex = nodesToLaunch.Dequeue(); } // If the provider is shutting down - don't launch the node if (shuttingDown) { nodeData[nodeIndex].NodeState = NodeState.NotLaunched; return; } try { // Either launch node or connect to an already running node InitializeNode(nodeIndex); if (!nodeData[nodeIndex].CommunicationFailed) { // Change the state of the node to launched lock (nodeStateLock) { nodeData[nodeIndex].NodeState = NodeState.Launched; } // Send all the requests to the node. Note that the requests may end up in // mixed order with the request currently being posted. LinkedListNode <BuildRequest> current = nodeData[nodeIndex].TargetList.First; BuildRequest[] buildRequests = new BuildRequest[nodeData[nodeIndex].TargetList.Count]; int i = 0; while (current != null) { buildRequests[i] = current.Value; i++; current = current.Next; } LocalCallDescriptorForPostBuildRequests callDescriptor = new LocalCallDescriptorForPostBuildRequests(buildRequests); nodeData[nodeIndex].NodeCommandQueue.Enqueue(callDescriptor); nodeData[nodeIndex].TargetList = null; } else { // Allow the engine to decide how to proceed since the node failed to launch string message = ResourceUtilities.FormatResourceString("NodeProviderFailure"); ReportNodeCommunicationFailure(nodeIndex, new Exception(message), false); } } catch (Exception e) { // Allow the engine to deal with the exception ReportNodeCommunicationFailure(nodeIndex, e, false); } }
private void ProcessBuildRequest(BuildRequest buildRequest) { ExecutionContext executionContext = GetExecutionContextFromHandleId(buildRequest.HandleId); // Restore the requests non-serialized data to the correct state buildRequest.RestoreNonSerializedDefaults(); buildRequest.NodeIndex = executionContext.NodeIndex; ErrorUtilities.VerifyThrow(buildRequest.ParentBuildEventContext != null, "Should not have a null parentBuildEventContext"); ErrorUtilities.VerifyThrow(buildRequest.IsGeneratedRequest == true, "Should not be sending a non generated request from the child node to the parent node"); // For buildRequests originating from the TEM - additional initialization is necessary TaskExecutionContext taskExecutionContext = executionContext as TaskExecutionContext; if (taskExecutionContext != null) { Project parentProject = taskExecutionContext.ParentProject; buildRequest.ParentHandleId = taskExecutionContext.TriggeringBuildRequest.HandleId; buildRequest.ParentRequestId = taskExecutionContext.TriggeringBuildRequest.RequestId; if (buildRequest.ToolsetVersion == null && parentProject.OverridingToolsVersion) { // If the MSBuild task (or whatever) didn't give us a specific tools version, // but the parent project is using an overridden tools version, then use that one buildRequest.ToolsetVersion = parentProject.ToolsVersion; } try { if (buildRequest.GlobalProperties == null) { try { // Make sure we have a blank global properties because if there is a problem merging them we wont have a crash when we try and cache the build result. buildRequest.GlobalProperties = new BuildPropertyGroup(); buildRequest.GlobalProperties = parentEngine.MergeGlobalProperties(parentProject.GlobalProperties, null, buildRequest.ProjectFileName, buildRequest.GlobalPropertiesPassedByTask); } catch (ArgumentException e) { ConvertToInvalidProjectException(buildRequest, parentProject, e); } catch (InvalidOperationException e) { ConvertToInvalidProjectException(buildRequest, parentProject, e); } } // We need to figure out which project object this request is refering to if (buildRequest.ProjectFileName == null) { ErrorUtilities.VerifyThrow(parentProject != null, "Parent project must be non-null"); // This means the caller (the MSBuild task) wants us to use the same project as the calling // project. This allows people to avoid passing in the Projects parameter on the MSBuild task. Project projectToBuild = parentProject; // If the parent project (the calling project) already has the same set of global properties // as what is being requested, just re-use it. Otherwise, we need to instantiate a new // project object that has the same project contents but different global properties. if (!projectToBuild.GlobalProperties.IsEquivalent(buildRequest.GlobalProperties) && (String.Compare(parentProject.ToolsVersion, buildRequest.ToolsetVersion, StringComparison.OrdinalIgnoreCase) == 0)) { projectToBuild = parentEngine.GetMatchingProject(parentProject, parentProject.FullFileName, buildRequest.GlobalProperties, buildRequest.ToolsetVersion, buildRequest.TargetNames, buildRequest.ParentBuildEventContext, buildRequest.ToolsVersionPeekedFromProjectFile); } buildRequest.ProjectToBuild = projectToBuild; buildRequest.ProjectFileName = projectToBuild.FullFileName; buildRequest.FireProjectStartedFinishedEvents = false; } } catch (InvalidProjectFileException e) { buildRequest.BuildCompleted = true; // Store message so it can be logged by the engine build loop buildRequest.BuildException = e; } } else { RequestRoutingContext requestRoutingContext = executionContext as RequestRoutingContext; buildRequest.ParentHandleId = requestRoutingContext.ParentHandleId; buildRequest.ParentRequestId = requestRoutingContext.ParentRequestId; } }
/// <summary> /// If there is an exception in process build request we will wrap it in an invalid project file exception as any exceptions caught here are really problems with a project file /// this exception will be handled in the engine and logged /// </summary> private static void ConvertToInvalidProjectException(BuildRequest buildRequest, Project parentProject, Exception e) { BuildEventFileInfo fileInfo = new BuildEventFileInfo(buildRequest.ProjectFileName); throw new InvalidProjectFileException(parentProject.FullFileName, fileInfo.Line, fileInfo.Column, fileInfo.EndLine, fileInfo.EndColumn, e.Message, null, null, null); }
/// <summary> /// This method specifies which node a particular build request has to be evaluated on. /// </summary>> /// <returns>Id of the node on which the build request should be performed</returns> internal int CalculateNodeForBuildRequest(BuildRequest currentRequest, int nodeIndexCurrent) { int nodeUsed = EngineCallback.inProcNode; if (childMode) { // If the project is already loaded on the current node or if the request // was sent from the parent - evaluate the request locally. In all other // cases send the request over to the parent if (nodeIndexCurrent != localNodeId && !currentRequest.IsExternalRequest) { // This is the same as using EngineCallback.parentNode nodeUsed = -1; } } else { // In single proc case return the current node if (nodes.Length == 1) { return(nodeUsed); } // If the project is not loaded either locally or on a remote node - calculate the best node to use // If there are nodes with less than "nodeWorkLoadProjectCount" projects in progress, choose the node // with the lowest in progress projects. Otherwise choose a node which has the least // number of projects loaded. Resolve a tie in number of projects loaded by looking at the number // of inprogress projects nodeUsed = nodeIndexCurrent; // If we have not chosen an node yet, this can happen if the node was loaded previously on a child node if (nodeUsed == EngineCallback.invalidNode) { if (useLoadBalancing) { #region UseLoadBalancing int blockedNode = EngineCallback.invalidNode; int blockedNodeRemainingProjectCount = nodeWorkLoadProjectCount; int leastBusyNode = EngineCallback.invalidNode; int leastBusyInProgressCount = -1; int leastLoadedNode = EngineCallback.inProcNode; int leastLoadedLoadCount = totalRequestsPerNode[EngineCallback.inProcNode]; int leastLoadedBlockedCount = blockedRequestsPerNode[EngineCallback.inProcNode]; for (int i = 0; i < nodes.Length; i++) { //postBlockCount indicates the number of projects which should be sent to a node to unblock it due to the //node running out of work. if (postBlockCount[i] != 0 && postBlockCount[i] < blockedNodeRemainingProjectCount) { blockedNode = i; blockedNodeRemainingProjectCount = postBlockCount[i]; } else { // Figure out which node has the least ammount of in progress work int perNodeInProgress = totalRequestsPerNode[i] - blockedRequestsPerNode[i]; if ((perNodeInProgress < nodeWorkLoadProjectCount) && (perNodeInProgress < leastBusyInProgressCount || leastBusyInProgressCount == -1)) { leastBusyNode = i; leastBusyInProgressCount = perNodeInProgress; } // Find the node with the least ammount of requests in total // or if the number of requests are the same find the node with the // node with the least number of blocked requests if ((totalRequestsPerNode[i] < leastLoadedLoadCount) || (totalRequestsPerNode[i] == leastLoadedLoadCount && blockedRequestsPerNode[i] < leastLoadedBlockedCount)) { leastLoadedNode = i; leastLoadedLoadCount = totalRequestsPerNode[i]; leastLoadedBlockedCount = perNodeInProgress; } } } // Give the work to a node blocked due to having no work . If there are no nodes without work // give the work to the least loaded node if (blockedNode != EngineCallback.invalidNode) { nodeUsed = blockedNode; postBlockCount[blockedNode]--; } else { nodeUsed = (leastBusyNode != EngineCallback.invalidNode) ? leastBusyNode : leastLoadedNode; } #endregion } else { // round robin schedule the build request nodeUsed = (lastUsedNode % nodes.Length); // Running total of the number of times this round robin scheduler has been called lastUsedNode++; if (postBlockCount[nodeUsed] != 0) { postBlockCount[nodeUsed]--; } } } // Update the internal data structure to reflect the scheduling decision NotifyOfSchedulingDecision(currentRequest, nodeUsed); } return(nodeUsed); }
/// <summary> /// Once the buildRequests from the EngineCallback have been created they are sent to this method which will /// post the build requests to the parent engine and then wait on the results to come back. /// This method uses either a breadthFirst or depthFirst traversal strategy when sending buildRequests to the parent engine. /// This method will start in breadthFirst traversal. It will continue to use this strategy until one of two events occur: /// 1. The parent node sents a message indicating the TEM should switch to depthFirst traversal. /// 2. The number of buildRequests is larger than the batchRequestSize. /// In both of these cases the system will go from a breadthFirstTraversal to a depthFirst Traversal. In the second case /// a message will be sent to the parent engine to switch the system to depthFirst traversal as the system is starting to /// be overloaded with work. /// In a depth first strategy the buildRequests will be sent to the parent engine one at a time and waiting for results for /// each buildRequest sent. In a breadthFirst traversal strategy some number of the buildrequests will be sent to the parent engine /// in a batch of requests. The system will then wait on the results of ALL the build requests sent before continuing /// to send more build requests. /// </summary> private void WaitForBuildResults(int handleId, BuildResult[] buildResultsLocal, BuildRequest[] buildRequests) { // If the traversal strategy is breadth first and the number of requests is less than the batchRequestSize // or if there is only 1 build request then send ALL build requests to the parent engine and wait on the results. if ((breadthFirstTraversal == true && buildRequests.Length < batchRequestSize) || buildRequests.Length == 1) { engineCallback.PostBuildRequestsToHost(buildRequests); workerThread.WaitForResults(handleId, buildResultsLocal, buildRequests); } else { int currentRequestIndex = 0; // Which build request is being processed int numberOfRequestsToSend = 0; // How many buildRequests are going to be sent based on the number of buildRequests remaining and the build request batch size. // Arrays that will be used to partion the buildRequests array when sending batches of builds requests at a time. BuildRequest[] wrapperArrayBreadthFirst = new BuildRequest[batchRequestSize]; BuildResult[] resultsArrayBreadthFirst = new BuildResult[batchRequestSize]; // Pre allocate these arrays as they will always be only one element in size. They are assigned to and filled when doing a depth first traversal. BuildRequest[] wrapperArrayDepthFirst = new BuildRequest[1]; BuildResult[] resultsArrayDepthFirst = new BuildResult[1]; // While there are still requests to send while (currentRequestIndex < buildRequests.Length) { // If there is a breadth first traversal and there are more than batchRequestSize build requests, send the first batchRequestSize, then do the rest depth first if (breadthFirstTraversal == true) { // Figure out how many requests to send, either the full batch size or only part of a batch numberOfRequestsToSend = (buildRequests.Length - currentRequestIndex) < batchRequestSize ? (buildRequests.Length - currentRequestIndex) : batchRequestSize; // Initialize the wrapper array to how many requests are going to be sent if (numberOfRequestsToSend != wrapperArrayBreadthFirst.Length) { wrapperArrayBreadthFirst = new BuildRequest[numberOfRequestsToSend]; resultsArrayBreadthFirst = new BuildResult[numberOfRequestsToSend]; } // Fill the wrapper array with one batch of build requests for (int i = 0; i < numberOfRequestsToSend; i++) { wrapperArrayBreadthFirst[i] = buildRequests[currentRequestIndex + i]; wrapperArrayBreadthFirst[i].RequestId = i; resultsArrayBreadthFirst[i] = null; } engineCallback.PostBuildRequestsToHost(wrapperArrayBreadthFirst); // Only switch from breadth to depth if there are more thanbatchRequestSize items if ((buildRequests.Length - currentRequestIndex) > batchRequestSize) { engineCallback.PostStatus(nodeId, new NodeStatus(false /* use depth first traversal*/), false /* don't block waiting on the send */); breadthFirstTraversal = false; } workerThread.WaitForResults(handleId, resultsArrayBreadthFirst, wrapperArrayBreadthFirst); Array.Copy(resultsArrayBreadthFirst, 0, buildResultsLocal, currentRequestIndex, numberOfRequestsToSend); currentRequestIndex += numberOfRequestsToSend; } // Proceed with depth first traversal while ((currentRequestIndex < buildRequests.Length) && !breadthFirstTraversal) { wrapperArrayDepthFirst[0] = buildRequests[currentRequestIndex]; buildRequests[currentRequestIndex].RequestId = 0; resultsArrayDepthFirst[0] = null; engineCallback.PostBuildRequestsToHost(wrapperArrayDepthFirst); workerThread.WaitForResults(handleId, resultsArrayDepthFirst, wrapperArrayDepthFirst); //Copy the result from an intermediate array to the full array buildResultsLocal[currentRequestIndex] = resultsArrayDepthFirst[0]; //Check if the call failed (null result was returned) if (buildResultsLocal[currentRequestIndex] == null) { return; } //Move to the next request currentRequestIndex++; } } } }
/// <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> /// For each target that has a cross node build request waiting for it to complete, iterate /// over the list of outstanding requests and find the matching out going request. Once /// the matching request is found - link the parent and child targets. /// </summary> private void LinkCrossNodeBuildRequests() { foreach (GraphNode node in dependencyGraph.Values) { TargetInProgessState.TargetIdWrapper[] parentsForBuildRequests = new TargetInProgessState.TargetIdWrapper[node.targetState.ParentBuildRequests.Count]; for (int j = 0; j < node.targetState.ParentBuildRequests.Count; j++) { BuildRequest buildRequest = node.targetState.ParentBuildRequests[j]; int nodeIndex = buildRequest.NodeIndex; int handleId = buildRequest.HandleId; int requestId = buildRequest.RequestId; bool foundParent = false; // Skip requests that originated from the host if (handleId == EngineCallback.invalidEngineHandle) { node.isRoot = true; continue; } // If the request being analyzed came from one of the child nodes, its incoming external request's // handleId will point at a routing context on the parent engine. If the outgoing request // orginated from another child the two requests (outgoing and incoming) point at different // routing contexts. In that case it is necessary to unwind the incoming request to the routing // context of the outgoing request. If outgoing request originated from the parent node - // there will be only one routing request. if (node.targetState.TargetId.nodeId != 0) { ExecutionContext executionContext = engineCallback.GetExecutionContextFromHandleId(buildRequest.HandleId); RequestRoutingContext routingContext = executionContext as RequestRoutingContext; if (routingContext != null && routingContext.ParentHandleId != EngineCallback.invalidEngineHandle) { ExecutionContext nextExecutionContext = engineCallback.GetExecutionContextFromHandleId(routingContext.ParentHandleId); if (nextExecutionContext is RequestRoutingContext) { nodeIndex = nextExecutionContext.NodeIndex; handleId = routingContext.ParentHandleId; requestId = routingContext.ParentRequestId; } } else { // Skip requests that originated from the host node.isRoot = true; continue; } } // Iterate over all outstanding requests until a match is found foreach (DictionaryEntry entry in outstandingExternalRequests) { BuildRequest[] externalRequests = (BuildRequest[])entry.Value; for (int i = 0; i < externalRequests.Length && !foundParent; i++) { if (handleId == externalRequests[i].HandleId && requestId == externalRequests[i].RequestId && nodeIndex == externalRequests[i].NodeIndex) { // Verify that the project name is the same ErrorUtilities.VerifyThrow( String.Compare(buildRequest.ProjectFileName, externalRequests[i].ProjectFileName, StringComparison.OrdinalIgnoreCase) == 0, "The two requests should have the same project name"); // Link the two graph nodes together GraphNode parentNode = (GraphNode)dependencyGraph[entry.Key]; parentNode.children.Add(node); parentsForBuildRequests[j] = parentNode.targetState.TargetId; foundParent = true; } } if (foundParent) { break; } } } node.targetState.ParentTargetsForBuildRequests = parentsForBuildRequests; } }
/// <summary> /// Get a cached build result if available for the given request. This method is thread safe. /// </summary> /// <param name="buildRequest"></param> /// <param name="actuallyBuiltTargets"></param> /// <returns></returns> internal BuildResult GetCachedBuildResult(BuildRequest buildRequest, out ArrayList actuallyBuiltTargets) { actuallyBuiltTargets = null; PropertyCacheEntry defaultTargetsCacheEntry, initialTargetsCacheEntry, projectIdCacheEntry; // No writes here, but since we're reading multiple values we want to get a consistent view of the cache cacheScopeReaderWriterLock.AcquireReaderLock(Timeout.Infinite); try { defaultTargetsCacheEntry = (PropertyCacheEntry)GetCacheEntry(Constants.defaultTargetCacheName); initialTargetsCacheEntry = (PropertyCacheEntry)GetCacheEntry(Constants.initialTargetCacheName); projectIdCacheEntry = (PropertyCacheEntry)GetCacheEntry(Constants.projectIdCacheName); } finally { cacheScopeReaderWriterLock.ReleaseReaderLock(); } // If we ever built anything in this project we must have the default and initial targets. if (defaultTargetsCacheEntry == null && initialTargetsCacheEntry == null) { return(null); } ErrorUtilities.VerifyThrow(projectIdCacheEntry != null, "We should always have the projectId cache entry"); ErrorUtilities.VerifyThrow(defaultTargetsCacheEntry != null && initialTargetsCacheEntry != null, "We should have both the initial and default targets in the cache"); ArrayList targetsToBuild = new ArrayList(initialTargetsCacheEntry.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); if (buildRequest.TargetNames == null || buildRequest.TargetNames.Length == 0) { targetsToBuild.AddRange(defaultTargetsCacheEntry.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)); } else { targetsToBuild.AddRange(buildRequest.TargetNames); } // Create variable to hold the cached outputs Hashtable outputsByTargetName = new Hashtable(targetsToBuild.Count); Hashtable resultByTarget = new Hashtable(targetsToBuild.Count, StringComparer.OrdinalIgnoreCase); bool overallSuccess = true; bool missingValues = false; // No writes here, but since we're reading multiple values we want to get a consistent view of the cache cacheScopeReaderWriterLock.AcquireReaderLock(Timeout.Infinite); try { for (int i = 0; i < targetsToBuild.Count; i++) { string targetName = EscapingUtilities.UnescapeAll((string)targetsToBuild[i]); if (ContainsCacheEntry(targetName)) { BuildResultCacheEntry cacheEntry = (BuildResultCacheEntry)GetCacheEntry(targetName); overallSuccess = overallSuccess && cacheEntry.BuildResult; resultByTarget[targetName] = (cacheEntry.BuildResult) ? Target.BuildState.CompletedSuccessfully : Target.BuildState.CompletedUnsuccessfully; // Restore output items for successful targets if (cacheEntry.BuildResult) { outputsByTargetName[targetName] = cacheEntry.BuildItems; } // We found a failed target - cut the loop short else { break; } } else { missingValues = true; break; } } } finally { cacheScopeReaderWriterLock.ReleaseReaderLock(); } if (missingValues) { return(null); } actuallyBuiltTargets = targetsToBuild; return(new BuildResult(outputsByTargetName, resultByTarget, overallSuccess, buildRequest.HandleId, buildRequest.RequestId, int.Parse(projectIdCacheEntry.Value, CultureInfo.InvariantCulture), false /* use results cache */, defaultTargetsCacheEntry.Value, initialTargetsCacheEntry.Value, 0, 0, 0)); }
internal LocalCallDescriptorForPostBuildRequests(BuildRequest buildRequest) : base(LocalCallType.PostBuildRequests) { this.buildRequests = new BuildRequest[1]; this.buildRequests[0] = buildRequest; }
internal void CreateFromStream(BinaryReader reader) { #region TargetId if (reader.ReadByte() == 0) { targetId = null; } else { targetId = new TargetIdWrapper(); targetId.CreateFromStream(reader); } #endregion #region ParentTargets if (reader.ReadByte() == 0) { parentTargets = null; } else { int numberOfTargets = reader.ReadInt32(); parentTargets = new List <TargetIdWrapper>(numberOfTargets); for (int i = 0; i < numberOfTargets; i++) { if (reader.ReadByte() == 0) { parentTargets.Add(null); } else { TargetIdWrapper wrapper = new TargetIdWrapper(); wrapper.CreateFromStream(reader); parentTargets.Add(wrapper); } } } #endregion #region ParentBuildRequests if (reader.ReadByte() == 0) { parentBuildRequests = null; } else { int numberOfRequests = reader.ReadInt32(); parentBuildRequests = new List <BuildRequest>(numberOfRequests); for (int i = 0; i < numberOfRequests; i++) { if (reader.ReadByte() == 0) { parentBuildRequests.Add(null); } else { parentBuildRequests.Add(BuildRequest.CreateFromStream(reader)); } } } #endregion #region OutstandingBuildRequests if (reader.ReadByte() == 0) { outstandingBuildRequests = null; } else { int numberOfBuildRequests = reader.ReadInt32(); outstandingBuildRequests = new BuildRequest[numberOfBuildRequests]; for (int i = 0; i < numberOfBuildRequests; i++) { if (reader.ReadByte() == 0) { outstandingBuildRequests[i] = null; } else { outstandingBuildRequests[i] = BuildRequest.CreateFromStream(reader); } } } #endregion #region ParentTargetsForBuildRequests if (reader.ReadByte() == 0) { parentTargetsForBuildRequests = null; } else { int numberOfTargetsForBuildRequests = reader.ReadInt32(); parentTargetsForBuildRequests = new TargetIdWrapper[numberOfTargetsForBuildRequests]; for (int i = 0; i < numberOfTargetsForBuildRequests; i++) { if (reader.ReadByte() == 0) { parentTargetsForBuildRequests[i] = null; } else { TargetIdWrapper wrapper = new TargetIdWrapper(); wrapper.CreateFromStream(reader); parentTargetsForBuildRequests[i] = wrapper; } } } #endregion #region ProjectName if (reader.ReadByte() == 0) { projectName = null; } else { projectName = reader.ReadString(); } #endregion }