public TestTempPEBuildManager(IFileSystem fileSystem) : base(IProjectThreadingServiceFactory.Create(), IUnconfiguredProjectCommonServicesFactory.Create(threadingService: IProjectThreadingServiceFactory.Create()), IActiveWorkspaceProjectContextHostFactory.Create(), IActiveConfiguredProjectSubscriptionServiceFactory.Create(), null, fileSystem, IProjectFaultHandlerServiceFactory.Create(), null) { BuildManager = new TestBuildManager(this); AppliedValue = new ProjectVersionedValue <DesignTimeInputsItem>(new DesignTimeInputsItem() { OutputPath = "TempPE" }, ImmutableDictionary <NamedIdentity, IComparable> .Empty); }
/// <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); }
/// <summary> /// InitializeInnerCoreAsync is responsible for setting an initial AppliedValue. This value will be used by any UI thread calls that may happen /// before the first data flow blocks have been processed. If this method doesn't set a value then the system will block until the first blocks /// have been applied. /// </summary> protected override Task InitializeInnerCoreAsync(CancellationToken cancellationToken) { AppliedValue = new ProjectVersionedValue <DesignTimeInputsItem>(new DesignTimeInputsItem(), ImmutableDictionary.Create <NamedIdentity, IComparable>()); return(Task.CompletedTask); }