/// <summary> /// Runs all of the tasks for this target, batched as necessary. /// </summary> internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry requestEntry, ProjectLoggingContext projectLoggingContext, CancellationToken cancellationToken) { #if MSBUILDENABLEVSPROFILING try { string beginTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - Start", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8800, beginTargetBuild); #endif try { VerifyState(_state, TargetEntryState.Execution); ErrorUtilities.VerifyThrow(!_isExecuting, "Target {0} is already executing", _target.Name); _cancellationToken = cancellationToken; _isExecuting = true; // Generate the batching buckets. Note that each bucket will get a lookup based on the baseLookup. This lookup will be in its // own scope, which we will collapse back down into the baseLookup at the bottom of the function. List <ItemBucket> buckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); WorkUnitResult aggregateResult = new WorkUnitResult(); TargetLoggingContext targetLoggingContext = null; bool targetSuccess = false; int numberOfBuckets = buckets.Count; string projectFullPath = requestEntry.RequestConfiguration.ProjectFullPath; string parentTargetName = null; if (ParentEntry != null && ParentEntry.Target != null) { parentTargetName = ParentEntry.Target.Name; } for (int i = 0; i < numberOfBuckets; i++) { ItemBucket bucket = buckets[i]; // If one of the buckets failed, stop building. if (aggregateResult.ActionCode == WorkUnitActionCode.Stop) { break; } targetLoggingContext = projectLoggingContext.LogTargetBatchStarted(projectFullPath, _target, parentTargetName); WorkUnitResult bucketResult = null; targetSuccess = false; Lookup.Scope entryForInference = null; Lookup.Scope entryForExecution = null; try { // This isn't really dependency analysis. This is up-to-date checking. Based on this we will be able to determine if we should // run tasks in inference or execution mode (or both) or just skip them altogether. ItemDictionary <ProjectItemInstance> changedTargetInputs; ItemDictionary <ProjectItemInstance> upToDateTargetInputs; Lookup lookupForInference; Lookup lookupForExecution; // UNDONE: (Refactor) Refactor TargetUpToDateChecker to take a logging context, not a logging service. TargetUpToDateChecker dependencyAnalyzer = new TargetUpToDateChecker(requestEntry.RequestConfiguration.Project, _target, targetLoggingContext.LoggingService, targetLoggingContext.BuildEventContext); DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs); switch (dependencyResult) { // UNDONE: Need to enter/leave debugger scope properly for the <Target> element. case DependencyAnalysisResult.FullBuild: case DependencyAnalysisResult.IncrementalBuild: case DependencyAnalysisResult.SkipUpToDate: // Create the lookups used to hold the current set of properties and items lookupForInference = bucket.Lookup; lookupForExecution = bucket.Lookup.Clone(); // Push the lookup stack up one so that we are only modifying items and properties in that scope. entryForInference = lookupForInference.EnterScope("ExecuteTarget() Inference"); entryForExecution = lookupForExecution.EnterScope("ExecuteTarget() Execution"); // if we're doing an incremental build, we need to effectively run the task twice -- once // to infer the outputs for up-to-date input items, and once to actually execute the task; // as a result we need separate sets of item and property collections to track changes if (dependencyResult == DependencyAnalysisResult.IncrementalBuild) { // subset the relevant items to those that are up-to-date foreach (string itemType in upToDateTargetInputs.ItemTypes) { lookupForInference.PopulateWithItems(itemType, upToDateTargetInputs[itemType]); } // subset the relevant items to those that have changed foreach (string itemType in changedTargetInputs.ItemTypes) { lookupForExecution.PopulateWithItems(itemType, changedTargetInputs[itemType]); } } // We either have some work to do or at least we need to infer outputs from inputs. bucketResult = await ProcessBucket(taskBuilder, targetLoggingContext, GetTaskExecutionMode(dependencyResult), lookupForInference, lookupForExecution); // Now aggregate the result with the existing known results. There are four rules, assuming the target was not // skipped due to being up-to-date: // 1. If this bucket failed or was cancelled, the aggregate result is failure. // 2. If this bucket Succeeded and we have not previously failed, the aggregate result is a success. // 3. Otherwise, the bucket was skipped, which has no effect on the aggregate result. // 4. If the bucket's action code says to stop, then we stop, regardless of the success or failure state. if (dependencyResult != DependencyAnalysisResult.SkipUpToDate) { aggregateResult = aggregateResult.AggregateResult(bucketResult); } else { if (aggregateResult.ResultCode == WorkUnitResultCode.Skipped) { aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null)); } } // Pop the lookup scopes, causing them to collapse their values back down into the // bucket's lookup. // NOTE: this order is important because when we infer outputs, we are trying // to produce the same results as would be produced from a full build; as such // if we're doing both the infer and execute steps, we want the outputs from // the execute step to override the outputs of the infer step -- this models // the full build scenario more correctly than if the steps were reversed entryForInference.LeaveScope(); entryForInference = null; entryForExecution.LeaveScope(); entryForExecution = null; targetSuccess = (bucketResult != null) && (bucketResult.ResultCode == WorkUnitResultCode.Success); break; case DependencyAnalysisResult.SkipNoInputs: case DependencyAnalysisResult.SkipNoOutputs: // We have either no inputs or no outputs, so there is nothing to do. targetSuccess = true; break; } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TargetFinished. Otherwise, // the log is confusing. targetLoggingContext.LogInvalidProjectFileError(e); if (null != entryForInference) { entryForInference.LeaveScope(); } if (null != entryForExecution) { entryForExecution.LeaveScope(); } aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null)); } finally { // Don't log the last target finished event until we can process the target outputs as we want to attach them to the // last target batch. if (targetLoggingContext != null && i < numberOfBuckets - 1) { targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, null); targetLoggingContext = null; } } } // Produce the final results. List <TaskItem> targetOutputItems = new List <TaskItem>(); try { // If any legacy CallTarget operations took place, integrate them back in to the main lookup now. LeaveLegacyCallTargetScopes(); // Publish the items for each bucket back into the baseLookup. Note that EnterScope() was actually called on each // bucket inside of the ItemBucket constructor, which is why you don't see it anywhere around here. foreach (ItemBucket bucket in buckets) { bucket.LeaveScope(); } string targetReturns = _target.Returns; ElementLocation targetReturnsLocation = _target.ReturnsLocation; // If there are no targets in the project file that use the "Returns" attribute, that means that we // revert to the legacy behavior in the case where Returns is not specified (null, rather // than the empty string, which indicates no returns). Legacy behavior is for all // of the target's Outputs to be returned. // On the other hand, if there is at least one target in the file that uses the Returns attribute, // then all targets in the file are run according to the new behaviour (return nothing unless otherwise // specified by the Returns attribute). if (targetReturns == null) { if (!_target.ParentProjectSupportsReturnsAttribute) { targetReturns = _target.Outputs; targetReturnsLocation = _target.OutputsLocation; } } if (!String.IsNullOrEmpty(targetReturns)) { // Determine if we should keep duplicates. bool keepDupes = ConditionEvaluator.EvaluateCondition ( _target.KeepDuplicateOutputs, ParserOptions.AllowPropertiesAndItemLists, _expander, ExpanderOptions.ExpandPropertiesAndItems, requestEntry.ProjectRootDirectory, _target.KeepDuplicateOutputsLocation, projectLoggingContext.LoggingService, projectLoggingContext.BuildEventContext ); // NOTE: we need to gather the outputs in batches, because the output specification may reference item metadata // Also, we are using the baseLookup, which has possibly had changes made to it since the project started. Because of this, the // set of outputs calculated here may differ from those which would have been calculated at the beginning of the target. It is // assumed the user intended this. List <ItemBucket> batchingBuckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); if (keepDupes) { foreach (ItemBucket bucket in batchingBuckets) { targetOutputItems.AddRange(bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation)); } } else { HashSet <TaskItem> addedItems = new HashSet <TaskItem>(); foreach (ItemBucket bucket in batchingBuckets) { IList <TaskItem> itemsToAdd = bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation); foreach (TaskItem item in itemsToAdd) { if (!addedItems.Contains(item)) { targetOutputItems.Add(item); addedItems.Add(item); } } } } } } finally { if (targetLoggingContext != null) { // log the last target finished since we now have the target outputs. targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, targetOutputItems != null && targetOutputItems.Count > 0 ? targetOutputItems : null); } } _targetResult = new TargetResult(targetOutputItems.ToArray(), aggregateResult); if (aggregateResult.ResultCode == WorkUnitResultCode.Failed && aggregateResult.ActionCode == WorkUnitActionCode.Stop) { _state = TargetEntryState.ErrorExecution; } else { _state = TargetEntryState.Completed; } } finally { _isExecuting = false; } #if MSBUILDENABLEVSPROFILING } finally { string endTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - End", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8801, endTargetBuild); } #endif }
/// <summary> /// Helper method for tests of IsAnyOutOfDate. /// The setup required here suggests that the TargetDependencyAnalyzer /// class should be refactored. /// </summary> /// <param name="input1Time"></param> /// <param name="input2Time"></param> /// <param name="output1Time"></param> /// <param name="output2Time"></param> /// <param name="isUpToDate"></param> private void IsAnyOutOfDateTestHelper ( DateTime?input1Time, DateTime?input2Time, DateTime?output1Time, DateTime?output2Time, bool expectedAnyOutOfDate, bool includeInput1, bool includeInput2, bool includeOutput1, bool includeOutput2 ) { List <string> inputs = new List <string>(); List <string> outputs = new List <string>(); string input1 = "NONEXISTENT_FILE"; string input2 = "NONEXISTENT_FILE"; string output1 = "NONEXISTENT_FILE"; string output2 = "NONEXISTENT_FILE"; try { if (input1Time != null) { input1 = FileUtilities.GetTemporaryFile(); File.WriteAllText(input1, String.Empty); File.SetLastWriteTime(input1, (DateTime)input1Time); } if (input2Time != null) { input2 = FileUtilities.GetTemporaryFile(); File.WriteAllText(input2, String.Empty); File.SetLastWriteTime(input2, (DateTime)input2Time); } if (output1Time != null) { output1 = FileUtilities.GetTemporaryFile(); File.WriteAllText(output1, String.Empty); File.SetLastWriteTime(output1, (DateTime)output1Time); } if (output2Time != null) { output2 = FileUtilities.GetTemporaryFile(); File.WriteAllText(output2, String.Empty); File.SetLastWriteTime(output2, (DateTime)output2Time); } if (includeInput1) { inputs.Add(input1); } if (includeInput2) { inputs.Add(input2); } if (includeOutput1) { outputs.Add(output1); } if (includeOutput2) { outputs.Add(output2); } DependencyAnalysisLogDetail detail; Assert.Equal(expectedAnyOutOfDate, TargetUpToDateChecker.IsAnyOutOfDate(out detail, Directory.GetCurrentDirectory(), inputs, outputs)); } finally { if (File.Exists(input1)) { File.Delete(input1); } if (File.Exists(input2)) { File.Delete(input2); } if (File.Exists(output1)) { File.Delete(output1); } if (File.Exists(output2)) { File.Delete(output2); } } }
private void SimpleSymlinkInputCheck(DateTime symlinkWriteTime, DateTime targetWriteTime, DateTime outputWriteTime, bool expectedOutOfDate) { var inputs = new List <string>(); var outputs = new List <string>(); string inputTarget = "NONEXISTENT_FILE"; string inputSymlink = "NONEXISTENT_FILE"; string outputTarget = "NONEXISTENT_FILE"; try { inputTarget = FileUtilities.GetTemporaryFile(); _testOutputHelper.WriteLine($"Created input file {inputTarget}"); File.SetLastWriteTime(inputTarget, targetWriteTime); inputSymlink = FileUtilities.GetTemporaryFile(null, ".linkin", createFile: false); if (!CreateSymbolicLink(inputSymlink, inputTarget, 0)) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } // File.SetLastWriteTime on the symlink sets the target write time, // so set the symlink's write time the hard way using (SafeFileHandle handle = NativeMethodsShared.CreateFile( inputSymlink, NativeMethodsShared.GENERIC_READ | 0x100 /* FILE_WRITE_ATTRIBUTES */, NativeMethodsShared.FILE_SHARE_READ, IntPtr.Zero, NativeMethodsShared.OPEN_EXISTING, NativeMethodsShared.FILE_ATTRIBUTE_NORMAL | NativeMethodsShared.FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero)) { if (handle.IsInvalid) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } long symlinkWriteTimeTicks = symlinkWriteTime.ToFileTimeUtc(); if (SetFileTime(handle, ref symlinkWriteTimeTicks, ref symlinkWriteTimeTicks, ref symlinkWriteTimeTicks) != true) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } } _testOutputHelper.WriteLine($"Created input link {inputSymlink}"); outputTarget = FileUtilities.GetTemporaryFile(); _testOutputHelper.WriteLine($"Created output file {outputTarget}"); File.SetLastWriteTime(outputTarget, outputWriteTime); inputs.Add(inputSymlink); outputs.Add(outputTarget); DependencyAnalysisLogDetail detail; Assert.Equal(expectedOutOfDate, TargetUpToDateChecker.IsAnyOutOfDate(out detail, Directory.GetCurrentDirectory(), inputs, outputs)); } finally { if (File.Exists(inputTarget)) { File.Delete(inputTarget); } if (File.Exists(inputSymlink)) { File.Delete(inputSymlink); } if (File.Exists(outputTarget)) { File.Delete(outputTarget); } } }
private DependencyAnalysisResult PerformDependencyAnalysisTestHelper ( FileWriteInfo[] filesToAnalyze, ItemDictionary <ProjectItemInstance> itemsByName, string inputs, string outputs, out ItemDictionary <ProjectItemInstance> changedTargetInputs, out ItemDictionary <ProjectItemInstance> upToDateTargetInputs ) { List <string> filesToDelete = new List <string>(); try { // first set the disk up for (int i = 0; i < filesToAnalyze.Length; ++i) { string path = ObjectModelHelpers.CreateFileInTempProjectDirectory(filesToAnalyze[i].Path, ""); File.SetCreationTime(path, filesToAnalyze[i].LastWriteTime); File.SetLastWriteTime(path, filesToAnalyze[i].LastWriteTime); filesToDelete.Add(path); } // Wait Thread.Sleep(50); // now create the project string unformattedProjectXml = ObjectModelHelpers.CleanupFileContents( @"<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'> <Target Name='Build' Inputs=""{0}"" Outputs=""{1}""> </Target> </Project>" ); string projectFile = Path.Combine(ObjectModelHelpers.TempProjectDir, "temp.proj"); string formattedProjectXml = String.Format(unformattedProjectXml, inputs, outputs); File.WriteAllText(projectFile, formattedProjectXml); // Wait Thread.Sleep(50); filesToDelete.Add(projectFile); Project project = new Project(projectFile); ProjectInstance p = project.CreateProjectInstance(); // now do the dependency analysis ItemBucket itemBucket = new ItemBucket(null, null, new Lookup(itemsByName, new PropertyDictionary <ProjectPropertyInstance>(), null), 0); TargetUpToDateChecker analyzer = new TargetUpToDateChecker(p, p.Targets["Build"], _mockHost, BuildEventContext.Invalid); return(analyzer.PerformDependencyAnalysis(itemBucket, out changedTargetInputs, out upToDateTargetInputs)); } finally { // finally clean up foreach (string path in filesToDelete) { if (File.Exists(path)) { File.Delete(path); } } ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); } }
private DependencyAnalysisResult PerformDependencyAnalysisTestHelper ( FileWriteInfo[] filesToAnalyze, ItemDictionary<ProjectItemInstance> itemsByName, string inputs, string outputs, out ItemDictionary<ProjectItemInstance> changedTargetInputs, out ItemDictionary<ProjectItemInstance> upToDateTargetInputs ) { List<string> filesToDelete = new List<string>(); try { // first set the disk up for (int i = 0; i < filesToAnalyze.Length; ++i) { string path = ObjectModelHelpers.CreateFileInTempProjectDirectory(filesToAnalyze[i].Path, ""); File.SetCreationTime(path, filesToAnalyze[i].LastWriteTime); File.SetLastWriteTime(path, filesToAnalyze[i].LastWriteTime); filesToDelete.Add(path); } // Wait Thread.Sleep(50); // now create the project string unformattedProjectXml = ObjectModelHelpers.CleanupFileContents( @"<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'> <Target Name='Build' Inputs=""{0}"" Outputs=""{1}""> </Target> </Project>"); string projectFile = Path.Combine(ObjectModelHelpers.TempProjectDir, "temp.proj"); string formattedProjectXml = String.Format(unformattedProjectXml, inputs, outputs); File.WriteAllText(projectFile, formattedProjectXml); // Wait Thread.Sleep(50); filesToDelete.Add(projectFile); Project project = new Project(projectFile); ProjectInstance p = project.CreateProjectInstance(); // now do the dependency analysis ItemBucket itemBucket = new ItemBucket(null, null, new Lookup(itemsByName, new PropertyDictionary<ProjectPropertyInstance>(), null), 0); TargetUpToDateChecker analyzer = new TargetUpToDateChecker(p, p.Targets["Build"], _mockHost, BuildEventContext.Invalid); return analyzer.PerformDependencyAnalysis(itemBucket, out changedTargetInputs, out upToDateTargetInputs); } finally { // finally clean up foreach (string path in filesToDelete) { if (File.Exists(path)) File.Delete(path); } ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); } }