/// <summary> /// Determines equivalence with another object of the same type. /// </summary> /// <param name="other">The other object with which to compare this one.</param> /// <returns>True if the objects are equivalent, false otherwise.</returns> private bool InternalEquals(FullyQualifiedBuildRequest other) { if (Object.ReferenceEquals(this, other)) { return(true); } if (_requestConfiguration != other._requestConfiguration) { return(false); } if (_resultsNeeded != other._resultsNeeded) { return(false); } if (_targets.Length != other._targets.Length) { return(false); } for (int i = 0; i < _targets.Length; ++i) { if (_targets[i] != other._targets[i]) { return(false); } } return(true); }
public void TestConstructorBad1() { Assert.Throws<ArgumentNullException>(() => { FullyQualifiedBuildRequest request = new FullyQualifiedBuildRequest(null, new string[1] { "foo" }, true); } ); }
public void TestConstructorBad2() { Assert.Throws<ArgumentNullException>(() => { FullyQualifiedBuildRequest request = new FullyQualifiedBuildRequest(new BuildRequestConfiguration(new BuildRequestData("foo", new Dictionary<string, string>(), "tools", new string[0], null), "2.0"), null, true); } ); }
public void TestConstructorGood() { BuildRequestData data1 = new BuildRequestData("foo", new Dictionary<string, string>(), "tools", new string[0], null); FullyQualifiedBuildRequest request = new FullyQualifiedBuildRequest(new BuildRequestConfiguration(data1, "2.0"), new string[1] { "foo" }, true); request = new FullyQualifiedBuildRequest(new BuildRequestConfiguration(data1, "2.0"), new string[0] { }, true); BuildRequestData data3 = new BuildRequestData("foo", new Dictionary<string, string>(), "tools", new string[0], null); request = new FullyQualifiedBuildRequest(new BuildRequestConfiguration(data1, "2.0"), new string[0] { }, false); }
public void TestProperties() { BuildRequestData data = new BuildRequestData("foo", new Dictionary<string, string>(), "tools", new string[0], null); BuildRequestConfiguration config = new BuildRequestConfiguration(data, "2.0"); FullyQualifiedBuildRequest request = new FullyQualifiedBuildRequest(config, new string[1] { "foo" }, true); Assert.AreEqual(request.Config, config); Assert.AreEqual(request.Targets.Length, 1); Assert.AreEqual(request.Targets[0], "foo"); Assert.AreEqual(request.ResultsNeeded, true); }
/// <summary> /// Determines equivalence with another object of the same type. /// </summary> /// <param name="other">The other object with which to compare this one.</param> /// <returns>True if the objects are equivalent, false otherwise.</returns> private bool InternalEquals(FullyQualifiedBuildRequest other) { if (ReferenceEquals(this, other)) { return(true); } if (Config != other.Config) { return(false); } if (ResultsNeeded != other.ResultsNeeded) { return(false); } if (BuildRequestDataFlags != other.BuildRequestDataFlags) { return(false); } if (Targets.Length != other.Targets.Length) { return(false); } for (int i = 0; i < Targets.Length; ++i) { if (Targets[i] != other.Targets[i]) { return(false); } } return(true); }
public void TestConstructorBad2() { FullyQualifiedBuildRequest request = new FullyQualifiedBuildRequest(new BuildRequestConfiguration(new BuildRequestData("foo", new Dictionary<string, string>(), "tools", new string[0], null), "2.0"), null, true); }
/// <summary> /// Create a new unsubmitted request entry /// </summary> /// <param name="sourceEntry">The build request originating these requests.</param> /// <param name="newRequests">The new requests to be issued.</param> public PendingUnsubmittedBuildRequests(BuildRequestEntry sourceEntry, FullyQualifiedBuildRequest[] newRequests) { this.SourceEntry = sourceEntry; this.NewRequests = newRequests; this.BlockingGlobalRequestId = BuildRequest.InvalidGlobalRequestId; this.BlockingTarget = null; }
/// <summary> /// Gets the results uses to continue the current build request. /// </summary> private BuildResult[] GetResultsForContinuation(FullyQualifiedBuildRequest[] requests, bool isContinue) { IDictionary<int, BuildResult> results; results = _continueResults; _continueResults = null; if (results == null) { // This catches the case where an MSBuild call is making a series of non-parallel build requests after Cancel has been // invoked. In this case, the wait above will immediately fall through and there will be no results. We just need to be // sure that we return a complete set of results which indicate we are aborting. ErrorUtilities.VerifyThrow(!isContinue, "Unexpected null results during continue"); results = new Dictionary<int, BuildResult>(); for (int i = 0; i < requests.Length; i++) { results[i] = new BuildResult(new BuildRequest(), new BuildAbortedException()); } } // See if we got any exceptions we should throw. foreach (BuildResult result in results.Values) { if (result.CircularDependency) { throw new CircularDependencyException(); } } // The build results will have node request IDs in the same order as the requests were issued, // which is in the array order above. List<BuildResult> resultsList = new List<BuildResult>(results.Values); resultsList.Sort(delegate (BuildResult left, BuildResult right) { if (left.NodeRequestId < right.NodeRequestId) { return -1; } else if (left.NodeRequestId == right.NodeRequestId) { return 0; } return 1; }); return resultsList.ToArray(); }
public void TestConstructorBad1() { FullyQualifiedBuildRequest request = new FullyQualifiedBuildRequest(null, new string[1] { "foo" }, true); }
private void NewBuildRequestsCallback(BuildRequestEntry entry, FullyQualifiedBuildRequest[] requests) { _newBuildRequests_FQRequests = requests; _newBuildRequests_BuildRequests = new BuildRequest[requests.Length]; _newBuildRequests_Entry = entry; int index = 0; foreach (FullyQualifiedBuildRequest request in requests) { IConfigCache configCache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestConfiguration matchingConfig = configCache.GetMatchingConfiguration(request.Config); BuildRequest newRequest = CreateNewBuildRequest(matchingConfig.ConfigurationId, request.Targets); entry.WaitForResult(newRequest); _newBuildRequests_BuildRequests[index++] = newRequest; } _newBuildRequestsEvent.Set(); }
internal void SetNewBuildRequests(FullyQualifiedBuildRequest[] requests) { _newRequests = requests; }
/// <summary> /// Thread to process the build request /// </summary> private void BuilderThreadProc() { bool completeSuccess = true; WaitHandle[] handles = new WaitHandle[2] { _cancelEvent, _continueEvent }; _threadStarted.Set(); // Add a request for each of the referenced projects. All we need to do is to make sure that the new project definition for the referenced // project has been added to the host collection FullyQualifiedBuildRequest[] fq = new FullyQualifiedBuildRequest[_currentProjectDefinition.ChildDefinitions.Count]; int fqCount = 0; foreach (RequestDefinition childDefinition in _currentProjectDefinition.ChildDefinitions) { BuildRequestConfiguration unresolvedConfig = childDefinition.UnresolvedConfiguration; fq[fqCount++] = new FullyQualifiedBuildRequest(unresolvedConfig, childDefinition.TargetsToBuild, true); } try { // Check to see if there was a cancel before we do anything if (_cancelEvent.WaitOne(1, false)) { HandleCancel(); return; } // Submit the build request for the references if we have any if (fqCount > 0) { OnNewBuildRequests(_requestedEntry, fq); // Wait for all of them to complete till our entry is marked ready int evt = WaitHandle.WaitAny(handles); // If a cancel occurs then we are done. Set the result to an exception if (evt == 0) { HandleCancel(); return; } // If we get a continue then one of the reference has complete. Set the result in the cache only in case of success. // Even though there may have been error - we cannot abandone the loop as there are already // requests in progress which may call back to this thread else if (evt == 1) { IDictionary<int, BuildResult> results = _requestedEntry.Continue(); foreach (BuildResult configResult in results.Values) { if (configResult.OverallResult == BuildResultCode.Failure) { completeSuccess = false; } else { _resultsCache.AddResult(configResult); } } } } // Check to see if there was a cancel we process the final result if (_cancelEvent.WaitOne(1, false)) { HandleCancel(); return; } // Simulate execution time for the actual entry if one was specified and if the entry built successfully if (_currentProjectDefinition.ExecutionTime > 0 && completeSuccess == true) { Thread.Sleep(_currentProjectDefinition.ExecutionTime); } // Create and send the result BuildResult result = new BuildResult(_requestedEntry.Request); // No specific target was asked to build. Return the default result if (_requestedEntry.Request.Targets.Count == 0) { result.AddResultsForTarget(RequestDefinition.defaultTargetName, new TargetResult(new TaskItem[1], completeSuccess ? TestUtilities.GetSuccessResult() : TestUtilities.GetStopWithErrorResult())); } else { foreach (string target in _requestedEntry.Request.Targets) { result.AddResultsForTarget(target, new TargetResult(new TaskItem[1], completeSuccess ? TestUtilities.GetSuccessResult() : TestUtilities.GetStopWithErrorResult())); } } _resultsCache.AddResult(result); _requestedEntry.Complete(result); RaiseRequestComplete(_requestedEntry); return; } catch (Exception e) { if (_requestedEntry != null) { string message = String.Format("Test: Unhandeled exception occured: \nMessage: {0} \nStack:\n{1}", e.Message, e.StackTrace); BuildResult errorResult = new BuildResult(_requestedEntry.Request, new InvalidOperationException(message)); _requestedEntry.Complete(errorResult); RaiseRequestComplete(_requestedEntry); } } }
public void TestRequestWithReferenceCancelled() { BuildRequestConfiguration configuration = CreateTestProject(1); try { TestTargetBuilder targetBuilder = (TestTargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache configCache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); FullyQualifiedBuildRequest[] newRequest = new FullyQualifiedBuildRequest[1] { new FullyQualifiedBuildRequest(configuration, new string[1] { "testTarget2" }, true) }; targetBuilder.SetNewBuildRequests(newRequest); configCache.AddConfiguration(configuration); BuildRequest request = CreateNewBuildRequest(1, new string[1] { "target1" }); BuildRequestEntry entry = new BuildRequestEntry(request, configuration); BuildResult result = new BuildResult(request); result.AddResultsForTarget("target1", GetEmptySuccessfulTargetResult()); targetBuilder.SetResultsToReturn(result); _requestBuilder.BuildRequest(GetNodeLoggingContext(), entry); WaitForEvent(_newBuildRequestsEvent, "New Build Requests"); Assert.Equal(_newBuildRequests_Entry, entry); ObjectModelHelpers.AssertArrayContentsMatch(_newBuildRequests_FQRequests, newRequest); BuildResult newResult = new BuildResult(_newBuildRequests_BuildRequests[0]); newResult.AddResultsForTarget("testTarget2", GetEmptySuccessfulTargetResult()); entry.ReportResult(newResult); _requestBuilder.ContinueRequest(); Thread.Sleep(500); _requestBuilder.CancelRequest(); WaitForEvent(_buildRequestCompletedEvent, "Build Request Completed"); Assert.Equal(BuildRequestEntryState.Complete, entry.State); Assert.Equal(entry, _buildRequestCompleted_Entry); Assert.Equal(BuildResultCode.Failure, _buildRequestCompleted_Entry.Result.OverallResult); } finally { DeleteTestProject(configuration); } }
/// <summary> /// This is called back when this request needs to issue new requests and possible wait on them. This method will /// block the builder's thread if any of the requests require us to wait for their results. /// </summary> /// <param name="requests">The list of build requests to be built.</param> /// <returns>The results, or null if the build should terminate.</returns> private async Task<BuildResult[]> StartNewBuildRequests(FullyQualifiedBuildRequest[] requests) { // Determine if we need to wait for results from any of these requests. // UNDONE: Currently we never set ResultsNeeded to anything but true. The purpose of this flag would be // to issue another top-level build request which no other request depends on, but which must finish in order for // the build to be considered complete. This would be brand new semantics. bool waitForResults = false; foreach (FullyQualifiedBuildRequest request in requests) { if (request.ResultsNeeded) { waitForResults = true; break; } } _blockType = BlockType.BlockedOnChildRequests; // Save the current operating environment, if necessary if (waitForResults) { SaveOperatingEnvironment(); } // Issue the requests to the engine RaiseOnNewBuildRequests(requests); // TODO: OPTIMIZATION: By returning null here, we commit to having to unwind the stack all the // way back to RequestThreadProc and then shutting down the thread before we can receive the // results and continue with them. It is not always the case that this will be desirable, however, // particularly if the results we need are immediately available. In those cases, it would be // useful to wait here for a short period in case those results become available - one second // might be enough. This means we may occasionally get more than one builder thread lying around // waiting for something to happen, but that would be short lived. At the same time it would // allow these already-available results to be utilized immediately without the unwind // semantics. // Now wait for results if we are supposed to. BuildResult[] results; if (waitForResults) { WaitHandle[] handles = new WaitHandle[] { _terminateEvent, _continueEvent }; int handle; if (IsBuilderUsingLegacyThreadingSemantics(_componentHost, _requestEntry)) { handle = RequestBuilder.WaitWithBuilderThreadStart(handles, true, _componentHost.LegacyThreadingData, _requestEntry.Request.SubmissionId); } else if (_inMSBuildCallback) { CultureInfo savedCulture = CultureInfo.CurrentCulture; CultureInfo savedUICulture = CultureInfo.CurrentUICulture; handle = await handles.ToTask(); Thread.CurrentThread.CurrentCulture = savedCulture; Thread.CurrentThread.CurrentUICulture = savedUICulture; } else { handle = WaitHandle.WaitAny(handles); } // If this is not a shutdown case, then the entry should be in the active state. if (handle == 1) { // Restore the operating environment. RestoreOperatingEnvironment(); VerifyEntryInActiveState(); } results = GetResultsForContinuation(requests, handle == 1); } else { results = new BuildResult[] { }; } ErrorUtilities.VerifyThrow(requests.Length == results.Length, "# results != # requests"); _blockType = BlockType.Unblocked; return results; }
/// <summary> /// Not Implemented /// </summary> private void MockIRequestBuilderCallback_OnNewBuildRequests(BuildRequestEntry sourceEntry, FullyQualifiedBuildRequest[] requests) { throw new NotImplementedException(); }
/// <summary> /// This method is responsible for evaluating whether we have enough information to make the request of the Build Manager, /// or if we need to obtain additional configuration information. It then issues either configuration /// requests or build requests, or both as needed. /// </summary> /// <param name="issuingEntry">The BuildRequestEntry which is making the request</param> /// <param name="newRequests">The array of "child" build requests to be issued.</param> /// <remarks> /// When we receive a build request, we first have to determine if we already have a configuration which matches the /// one used by the request. We do this because everywhere we deal with requests and results beyond this function, we /// use configuration ids, which are assigned once by the Build Manager and are global to the system. If we do /// not have a global configuration id, we can't check to see if we already have build results for the request, so we /// cannot send the request out. Thus, first we determine the configuration id. /// /// Assuming we don't have the global configuration id locally, we will send the configuration to the Build Manager. /// It will look up or assign the global configuration id and send it back to us. /// /// Once we have the global configuration id, we can then look up results locally. If we have enough results to fulfill /// the request, we give them back to the request, otherwise we have to forward the request to the Build Mangager /// for scheduling. /// </remarks> private void IssueBuildRequests(BuildRequestEntry issuingEntry, FullyQualifiedBuildRequest[] newRequests) { ErrorUtilities.VerifyThrow(_componentHost != null, "No host object set"); // For each request, we need to determine if we have a local configuration in the // configuration cache. If we do, we can issue the build request immediately. // Otherwise, we need to ask the Build Manager for configuration IDs and issue those requests // later. IConfigCache globalConfigCache = (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache); // We are going to potentially issue several requests. We don't want the state of the request being modified by // other threads while this occurs, so we lock the request for the duration. This lock is the same lock // used by the BuildRequestEntry itself to lock each of its data-modifying methods, effectively preventing // any other thread from modifying the BuildRequestEntry while we hold it. This mechanism also means that it // is not necessary for other threads to take the global lock explicitly if they are just doing single operations // to the entry rather than a series of them. lock (issuingEntry.GlobalLock) { List<BuildResult> existingResultsToReport = new List<BuildResult>(); HashSet<NGen<int>> unresolvedConfigurationsAdded = new HashSet<NGen<int>>(); foreach (FullyQualifiedBuildRequest request in newRequests) { // Do we have a matching configuration? BuildRequestConfiguration matchingConfig = globalConfigCache.GetMatchingConfiguration(request.Config); BuildRequest newRequest = null; if (matchingConfig == null) { // No configuration locally, are we already waiting for it? matchingConfig = _unresolvedConfigurations.GetMatchingConfiguration(request.Config); if (matchingConfig == null) { // Not waiting for it request.Config.ConfigurationId = GetNextUnresolvedConfigurationId(); _unresolvedConfigurations.AddConfiguration(request.Config); unresolvedConfigurationsAdded.Add(request.Config.ConfigurationId); } else { request.Config.ConfigurationId = matchingConfig.ConfigurationId; } // Whether we are already waiting for a configuration or we need to wait for another one // we will add this request as waiting for a configuration. As new configuration resolutions // come in, we will check our requests which are waiting for configurations move them to // waiting for results. It is important that we tell the issuing request to wait for a result // prior to issuing any necessary configuration request so that we don't get into a state where // we receive the configuration response before we enter the wait state. newRequest = new BuildRequest(issuingEntry.Request.SubmissionId, GetNextBuildRequestId(), request.Config.ConfigurationId, request.Targets, issuingEntry.Request.HostServices, issuingEntry.Request.BuildEventContext, issuingEntry.Request); issuingEntry.WaitForResult(newRequest); if (matchingConfig == null) { // Issue the config resolution request TraceEngine("Request {0}({1}) (nr {2}) is waiting on configuration {3} (IBR)", issuingEntry.Request.GlobalRequestId, issuingEntry.Request.ConfigurationId, issuingEntry.Request.NodeRequestId, request.Config.ConfigurationId); issuingEntry.WaitForConfiguration(request.Config); } } else { // We have a configuration, see if we already have results locally. newRequest = new BuildRequest(issuingEntry.Request.SubmissionId, GetNextBuildRequestId(), matchingConfig.ConfigurationId, request.Targets, issuingEntry.Request.HostServices, issuingEntry.Request.BuildEventContext, issuingEntry.Request); IResultsCache resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); ResultsCacheResponse response = resultsCache.SatisfyRequest(newRequest, matchingConfig.ProjectInitialTargets, matchingConfig.ProjectDefaultTargets, matchingConfig.GetAfterTargetsForDefaultTargets(newRequest), skippedResultsAreOK: false); if (response.Type == ResultsCacheResponseType.Satisfied) { // We have a result, give it back to this request. issuingEntry.WaitForResult(newRequest); // Log the fact that we handled this from the cache. _nodeLoggingContext.LogRequestHandledFromCache(newRequest, _configCache[newRequest.ConfigurationId], response.Results); // Can't report the result directly here, because that could cause the request to go from // Waiting to Ready. existingResultsToReport.Add(response.Results); } else { // No result, to wait for it. issuingEntry.WaitForResult(newRequest); } } } // If we have any results we had to report, do so now. foreach (BuildResult existingResult in existingResultsToReport) { issuingEntry.ReportResult(existingResult); } // Issue any configuration requests we may still need. List<BuildRequestConfiguration> unresolvedConfigurationsToIssue = issuingEntry.GetUnresolvedConfigurationsToIssue(); if (unresolvedConfigurationsToIssue != null) { foreach (BuildRequestConfiguration unresolvedConfigurationToIssue in unresolvedConfigurationsToIssue) { unresolvedConfigurationsAdded.Remove(unresolvedConfigurationToIssue.ConfigurationId); IssueConfigurationRequest(unresolvedConfigurationToIssue); } } // Remove any configurations we ended up not waiting for, otherwise future requests will think we are still waiting for them // and will never get submitted. foreach (int unresolvedConfigurationId in unresolvedConfigurationsAdded) { _unresolvedConfigurations.RemoveConfiguration(unresolvedConfigurationId); } // Finally, if we can issue build requests, do so. List<BuildRequest> requestsToIssue = issuingEntry.GetRequestsToIssueIfReady(); if (requestsToIssue != null) { BuildRequestBlocker blocker = new BuildRequestBlocker(issuingEntry.Request.GlobalRequestId, issuingEntry.GetActiveTargets(), requestsToIssue.ToArray()); IssueBuildRequest(blocker); } if (issuingEntry.State == BuildRequestEntryState.Ready) { ErrorUtilities.VerifyThrow((requestsToIssue == null) || (requestsToIssue.Count == 0), "Entry shouldn't be ready if we also issued requests."); ActivateBuildRequest(issuingEntry); } } }
/// <summary> /// Invokes the OnNewBuildRequests event /// </summary> /// <param name="requests">The requests to be fulfilled.</param> private void RaiseOnNewBuildRequests(FullyQualifiedBuildRequest[] requests) { NewBuildRequestsDelegate newRequestDelegate = OnNewBuildRequests; if (null != newRequestDelegate) { newRequestDelegate(_requestEntry, requests); } }
/// <summary> /// Determines equivalence with another object of the same type. /// </summary> /// <param name="other">The other object with which to compare this one.</param> /// <returns>True if the objects are equivalent, false otherwise.</returns> private bool InternalEquals(FullyQualifiedBuildRequest other) { if (Object.ReferenceEquals(this, other)) { return true; } if (_requestConfiguration != other._requestConfiguration) { return false; } if (_resultsNeeded != other._resultsNeeded) { return false; } if (_targets.Length != other._targets.Length) { return false; } for (int i = 0; i < _targets.Length; ++i) { if (_targets[i] != other._targets[i]) { return false; } } return true; }
/// <summary> /// Raised when the active request needs to build new requests. /// </summary> /// <param name="sourceEntry">The request issuing the requests.</param> /// <param name="newRequests">The requests being issued.</param> /// <remarks>Called by the RequestBuilder (implicitly through an event). Non-overlapping with other RequestBuilders.</remarks> private void Builder_OnNewBuildRequests(BuildRequestEntry sourceEntry, FullyQualifiedBuildRequest[] newRequests) { QueueAction( () => { _unsubmittedRequests.Enqueue(new PendingUnsubmittedBuildRequests(sourceEntry, newRequests)); IssueUnsubmittedRequests(); EvaluateRequestStates(); }, isLastTask: false); }
/// <summary> /// This method instructs the request builder to build the specified projects using the specified parameters. This is /// what is ultimately used by something like an MSBuild task which needs to invoke a project-to-project reference. IBuildEngine /// and IBuildEngine2 have BuildProjectFile methods which boil down to an invocation of this method as well. /// </summary> /// <param name="projectFiles">An array of projects to be built.</param> /// <param name="properties">The property groups to use for each project. Must be the same number as there are project files.</param> /// <param name="toolsVersions">The tools version to use for each project. Must be the same number as there are project files.</param> /// <param name="targets">The targets to be built. Each project will be built with the same targets.</param> /// <param name="waitForResults">True to wait for the results </param> /// <returns>True if the requests were satisfied, false if they were aborted.</returns> public async Task<BuildResult[]> BuildProjects(string[] projectFiles, PropertyDictionary<ProjectPropertyInstance>[] properties, string[] toolsVersions, string[] targets, bool waitForResults) { VerifyIsNotZombie(); ErrorUtilities.VerifyThrowArgumentNull(projectFiles, "projectFiles"); ErrorUtilities.VerifyThrowArgumentNull(properties, "properties"); ErrorUtilities.VerifyThrowArgumentNull(targets, "targets"); ErrorUtilities.VerifyThrowArgumentNull(toolsVersions, "toolsVersions"); ErrorUtilities.VerifyThrow(_componentHost != null, "No host object set"); ErrorUtilities.VerifyThrow(projectFiles.Length == properties.Length, "Properties and project counts not the same"); ErrorUtilities.VerifyThrow(projectFiles.Length == toolsVersions.Length, "Tools versions and project counts not the same"); FullyQualifiedBuildRequest[] requests = new FullyQualifiedBuildRequest[projectFiles.Length]; for (int i = 0; i < projectFiles.Length; ++i) { if (!Path.IsPathRooted(projectFiles[i])) { projectFiles[i] = Path.Combine(_requestEntry.ProjectRootDirectory, projectFiles[i]); } // Canonicalize projectFiles[i] = FileUtilities.NormalizePath(projectFiles[i]); // A tools version specified by an MSBuild task or similar has priority string explicitToolsVersion = toolsVersions[i]; // Otherwise go to any explicit tools version on the project who made this callback if (explicitToolsVersion == null && _requestEntry.RequestConfiguration.ExplicitToolsVersionSpecified) { explicitToolsVersion = _requestEntry.RequestConfiguration.ToolsVersion; } // Otherwise let the BuildRequestConfiguration figure out what tools version will be used BuildRequestData data = new BuildRequestData(projectFiles[i], properties[i].ToDictionary(), explicitToolsVersion, targets, null); BuildRequestConfiguration config = new BuildRequestConfiguration(data, _componentHost.BuildParameters.DefaultToolsVersion, _componentHost.BuildParameters.GetToolset); requests[i] = new FullyQualifiedBuildRequest(config, targets, waitForResults); } // Send the requests off BuildResult[] results = await StartNewBuildRequests(requests); ErrorUtilities.VerifyThrow(requests.Length == results.Length, "# results != # requests"); return results; }