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));
            }
Esempio n. 3
0
        /// <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);
            }
Esempio n. 4
0
 protected override bool ShouldValueBeApplied(DesignTimeInputsDelta previouslyAppliedOutput, DesignTimeInputsDelta newOutput)
 {
     return(newOutput.HasFileChanges || newOutput.HasProjectPropertyChanges);
 }
Esempio n. 5
0
        /// <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);
                }
            }
        }