public async Task <string> GetTempPEDescriptionXmlAsync(string moniker) { Requires.NotNull(moniker, nameof(moniker)); await InitializeAsync(); await _unconfiguredProjectServices.ThreadingService.SwitchToUIThread(); DesignTimeInputsItem inputs = AppliedValue.Value; if (!inputs.Inputs.Contains(moniker)) { throw new ArgumentException("Moniker supplied must be one of the DesignTime source files", nameof(moniker)); } string outputPath = inputs.OutputPath; string outputFileName = GetOutputFileName(outputPath, moniker); // For parity with legacy we don't care about the compilation result: Legacy only errors here if it runs out of memory queuing the compilation // Additionally for parity, we compile here on the UI thread and block (whilst still preventing simultaneous work) await ScheduleCompilationAsync(moniker, inputs, waitForCompletion : true, forceCompilation : false); // VSTypeResolutionService is the only consumer, and it only uses the codebase element so it's fine to default most of these (VC++ does the same) return($@"<root> <Application private_binpath = ""{outputPath}""/> <Assembly codebase = ""{outputFileName}"" name = ""{moniker}"" version = ""0.0.0.0"" snapshot_id = ""1"" replaceable = ""True"" /> </root>"); }
private void DisposeTaskSchedulers() { DesignTimeInputsItem value = AppliedValue?.Value; if (value == null) { return; } foreach (ITaskDelayScheduler scheduler in value.TaskSchedulers.Values) { scheduler.Dispose(); } }
private async Task ScheduleCompilationAsync(string moniker, DesignTimeInputsItem inputs, bool waitForCompletion, bool forceCompilation) { HashSet <string> files = GetFilesToCompile(moniker, inputs.SharedInputs); string outputFileName = GetOutputFileName(inputs.OutputPath, moniker); if (forceCompilation || CompilationNeeded(files, outputFileName)) { if (inputs.TaskSchedulers.TryGetValue(moniker, out ITaskDelayScheduler scheduler)) { if (waitForCompletion) { await scheduler.RunAsyncTask(token => CompileTempPEAsync(files, outputFileName, token)).Task; } else { _projectFaultHandlerService.Forget(scheduler.ScheduleAsyncTask(token => CompileTempPEAsync(files, outputFileName, token)).Task, UnconfiguredProject); } } } }
/// <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); }