internal void ProcessDataflowChanges(IProjectVersionedValue <DesignTimeInputsDelta> obj) { // Cancel any in-progress queue processing _compilationCancellationSource?.Cancel(); DesignTimeInputsDelta delta = obj.Value; // add all of the changes to our queue _queue.Update(delta.ChangedInputs, delta.Inputs, delta.SharedInputs, delta.TempPEOutputPath); // Create a cancellation source so we can cancel the compilation if another message comes through _compilationCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(_project.Services.ProjectAsynchronousTasks.UnloadCancellationToken); JoinableTask task = _scheduler.ScheduleAsyncTask(ProcessCompileQueueAsync, _compilationCancellationSource.Token); // For unit testing purposes, optionally block the thread until the task we scheduled is complete if (CompileSynchronously) { _threadingService.ExecuteSynchronously(() => task.Task); } }
public Task ApplyAsync(DesignTimeInputsDelta delta) { var input = IProjectVersionedValueFactory.Create(delta); return(base.ApplyAsync(input)); }
/// <summary> /// ApplyAsync is called on the UI thread and its job is to update AppliedValue to be correct based on the changes that have come through data flow after being processed /// </summary> protected override async Task ApplyAsync(DesignTimeInputsDelta value) { // Not using use the ThreadingService property because unit tests await _unconfiguredProjectServices.ThreadingService.SwitchToUIThread(); DesignTimeInputsItem previousValue = AppliedValue?.Value ?? new DesignTimeInputsItem(); // Calculate the new value ImmutableHashSet <string> newDesignTimeInputs; ImmutableHashSet <string> newSharedDesignTimeInputs; ImmutableDictionary <string, uint> newCookies; ImmutableDictionary <string, ITaskDelayScheduler> newSchedulers; var addedDesignTimeInputs = new List <string>(); var removedDesignTimeInputs = new List <string>(); bool hasRemovedDesignTimeSharedInputs = false; bool hasAddedDesignTimeSharedInputs = false; if (value.HasFileChanges) { var designTimeInputs = previousValue.Inputs.ToBuilder(); var designTimeSharedInputs = previousValue.SharedInputs.ToBuilder(); var cookies = previousValue.Cookies.ToBuilder(); var schedulers = previousValue.TaskSchedulers.ToBuilder(); foreach (string item in value.AddedItems) { if (designTimeInputs.Add(item)) { addedDesignTimeInputs.Add(item); schedulers.Add(item, CreateTaskScheduler()); } } foreach (string item in value.RemovedItems) { if (designTimeInputs.Remove(item)) { removedDesignTimeInputs.Add(item); // We only unsubscribe from file changes if there is no other reason to care about this file if (TryGetValueIfUnused(item, cookies, designTimeSharedInputs, out _)) { cookies.Remove(item); } if (TryGetValueIfUnused(item, schedulers, designTimeSharedInputs, out ITaskDelayScheduler scheduler)) { schedulers.Remove(item); scheduler.Dispose(); } } } foreach (string item in value.AddedSharedItems) { if (designTimeSharedInputs.Add(item)) { hasAddedDesignTimeSharedInputs = true; if (!schedulers.ContainsKey(item)) { schedulers.Add(item, CreateTaskScheduler()); } } } foreach (string item in value.RemovedSharedItems) { if (designTimeSharedInputs.Remove(item)) { hasRemovedDesignTimeSharedInputs = true; if (TryGetValueIfUnused(item, cookies, designTimeInputs, out _)) { cookies.Remove(item); } if (TryGetValueIfUnused(item, schedulers, designTimeInputs, out ITaskDelayScheduler scheduler)) { schedulers.Remove(item); scheduler.Dispose(); } } } newDesignTimeInputs = designTimeInputs.ToImmutable(); newSharedDesignTimeInputs = designTimeSharedInputs.ToImmutable(); newCookies = cookies.ToImmutable(); newSchedulers = schedulers.ToImmutable(); } else { // If there haven't been file changes we can just flow our previous collections to the new version to avoid roundtriping our collections to builders and back newDesignTimeInputs = previousValue.Inputs; newSharedDesignTimeInputs = previousValue.SharedInputs; newCookies = previousValue.Cookies; newSchedulers = previousValue.TaskSchedulers; } // Apply our new value AppliedValue = new ProjectVersionedValue <DesignTimeInputsItem>(new DesignTimeInputsItem { Inputs = newDesignTimeInputs, SharedInputs = newSharedDesignTimeInputs, // We always need an output path, so if it hasn't changed we just reuse the previous value OutputPath = value.OutputPath ?? previousValue.OutputPath, Cookies = newCookies, TaskSchedulers = newSchedulers }, value.DataSourceVersions); // Project properties changes cause all PEs to be dirty and recompile if this isn't the first update if (value.HasProjectPropertyChanges) { foreach (string item in newDesignTimeInputs) { await FireTempPEDirtyAsync(item, value.ShouldCompile); } } else { // Individual inputs dirty their PEs and possibly recompile foreach (string item in addedDesignTimeInputs) { await FireTempPEDirtyAsync(item, value.ShouldCompile); } } // Shared items cause all TempPEs to be dirty, but don't recompile, to match legacy behaviour if (hasRemovedDesignTimeSharedInputs || hasAddedDesignTimeSharedInputs) { // adding or removing shared design time inputs dirties things but doesn't recompile foreach (string item in newDesignTimeInputs) { // We don't want to fire again if we already fired above and compiled if (!addedDesignTimeInputs.Contains(item)) { await FireTempPEDirtyAsync(item, false); } } } foreach (string item in removedDesignTimeInputs) { BuildManager.OnDesignTimeOutputDeleted(item); }
protected override bool ShouldValueBeApplied(DesignTimeInputsDelta previouslyAppliedOutput, DesignTimeInputsDelta newOutput) { return(newOutput.HasFileChanges || newOutput.HasProjectPropertyChanges); }
/// <summary> /// Preprocess gets called as each data flow block updates and its job is to take the input from those blocks and do whatever work needed /// so that ApplyAsync has all of the info it needs to do its job. /// </summary> protected override Task <DesignTimeInputsDelta> PreprocessAsync(IProjectVersionedValue <InputTuple> input, DesignTimeInputsDelta previousOutput) { IProjectChangeDescription compileChanges = input.Value.Item1.ProjectChanges[Compile.SchemaName]; IProjectChangeDescription configChanges = input.Value.Item2.ProjectChanges[ConfigurationGeneral.SchemaName]; ImmutableArray <string> .Builder addedDesignTimeInputs = ImmutableArray.CreateBuilder <string>(); ImmutableArray <string> .Builder removedDesignTimeInputs = ImmutableArray.CreateBuilder <string>(); ImmutableArray <string> .Builder addedDesignTimeSharedInputs = ImmutableArray.CreateBuilder <string>(); ImmutableArray <string> .Builder removedDesignTimeSharedInputs = ImmutableArray.CreateBuilder <string>(); foreach (string item in compileChanges.Difference.AddedItems) { PreprocessAddItem(item); } foreach (string item in compileChanges.Difference.RemovedItems) { PreprocessRemoveItem(item); } foreach ((string oldName, string newName) in compileChanges.Difference.RenamedItems) { // A rename is just an add and a remove PreprocessAddItem(newName); PreprocessRemoveItem(oldName); } foreach (string item in compileChanges.Difference.ChangedItems) { (bool wasDesignTime, bool wasDesignTimeShared) = GetDesignTimePropsForItem(compileChanges.Before.Items[item]); (bool designTime, bool designTimeShared) = GetDesignTimePropsForItem(compileChanges.After.Items[item]); if (!wasDesignTime && designTime) { addedDesignTimeInputs.Add(item); } else if (wasDesignTime && !designTime) { removedDesignTimeInputs.Add(item); } if (!wasDesignTimeShared && designTimeShared) { addedDesignTimeSharedInputs.Add(item); } else if (wasDesignTimeShared && !designTimeShared) { removedDesignTimeSharedInputs.Add(item); } } bool namespaceChanged = configChanges.Difference.ChangedProperties.Contains(ConfigurationGeneral.RootNamespaceProperty); string outputPath = null; if (configChanges.Difference.ChangedProperties.Contains(ConfigurationGeneral.ProjectDirProperty) || configChanges.Difference.ChangedProperties.Contains(ConfigurationGeneral.IntermediateOutputPathProperty)) { string basePath = configChanges.After.Properties[ConfigurationGeneral.ProjectDirProperty]; string objPath = configChanges.After.Properties[ConfigurationGeneral.IntermediateOutputPathProperty]; outputPath = Path.Combine(basePath, objPath, "TempPE"); } var result = new DesignTimeInputsDelta { AddedItems = addedDesignTimeInputs.ToImmutable(), RemovedItems = removedDesignTimeInputs.ToImmutable(), AddedSharedItems = addedDesignTimeSharedInputs.ToImmutable(), RemovedSharedItems = removedDesignTimeSharedInputs.ToImmutable(), NamespaceChanged = namespaceChanged, OutputPath = outputPath, // if this is the first time processing (previousOutput = null) then we will be "adding" all inputs // but we don't want to immediately kick off a compile of all files. We'll let the events fire and // the file timestamp will determine if we compile when someone asks for the TempPEs ShouldCompile = (previousOutput != null), DataSourceVersions = input.DataSourceVersions }; return(Task.FromResult(result)); (bool designTime, bool designTimeShared) GetDesignTimePropsForItem(IImmutableDictionary <string, string> item) { item.TryGetValue(Compile.LinkProperty, out string linkString); item.TryGetValue(Compile.DesignTimeProperty, out string designTimeString); item.TryGetValue(Compile.DesignTimeSharedInputProperty, out string designTimeSharedString); if (linkString != null && linkString.Length > 0) { // Linked files are never used as TempPE inputs return(false, false); } return(StringComparers.PropertyLiteralValues.Equals(designTimeString, bool.TrueString), StringComparers.PropertyLiteralValues.Equals(designTimeSharedString, bool.TrueString)); } void PreprocessAddItem(string item) { (bool designTime, bool designTimeShared) = GetDesignTimePropsForItem(compileChanges.After.Items[item]); if (designTime) { addedDesignTimeInputs.Add(item); } // Legacy allows files to be DesignTime and DesignTimeShared if (designTimeShared) { addedDesignTimeSharedInputs.Add(item); } } void PreprocessRemoveItem(string item) { // Because the item has been removed we retreive its properties from the Before state (bool designTime, bool designTimeShared) = GetDesignTimePropsForItem(compileChanges.Before.Items[item]); if (designTime) { removedDesignTimeInputs.Add(item); } // Legacy allows files to be DesignTime and DesignTimeShared if (designTimeShared) { removedDesignTimeSharedInputs.Add(item); } } }