| | |
| | | using System;
|
| | | using System.Collections.Concurrent;
|
| | | using System.Collections.Generic;
|
| | | using System.IO;
|
| | | using System.Linq;
|
| | | using System.Threading;
|
| | | using System.Threading.Tasks;
|
| | | using System.Threading.Tasks.Dataflow;
|
| | | using Microsoft.Build.Evaluation;
|
| | | using Microsoft.Build.Framework;
|
| | | using Microsoft.VisualStudio.ProjectSystem;
|
| | | using Microsoft.VisualStudio.ProjectSystem.Properties;
|
| | | using Microsoft.VisualStudio.Shell;
|
| | | using Microsoft.VisualStudio.TaskStatusCenter;
|
| | | using Microsoft.VisualStudio.Threading;
|
| | | using EnvDTE;
|
| | | using Microsoft.VisualStudio.VCProjectEngine;
|
| | |
|
| | | using Task = System.Threading.Tasks.Task;
|
| | |
|
| | | namespace QtVsTools.QtMsBuild
|
| | | {
|
| | | using Common;
|
| | | using Core;
|
| | | using VisualStudio;
|
| | | using Thread = System.Threading.Thread;
|
| | |
|
| | | using SubscriberAction = ActionBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>>;
|
| | |
|
| | | class QtProjectTracker : Concurrent<QtProjectTracker>
|
| | | {
|
| | | static ConcurrentDictionary<string, QtProjectTracker> _Instances;
|
| | | static ConcurrentDictionary<string, QtProjectTracker> Instances =>
|
| | | StaticThreadSafeInit(() => _Instances, () =>
|
| | | _Instances = new ConcurrentDictionary<string, QtProjectTracker>());
|
| | | static LazyFactory StaticLazy { get; } = new LazyFactory();
|
| | |
|
| | | static PunisherQueue<QtProjectTracker> _InitQueue;
|
| | | static PunisherQueue<QtProjectTracker> InitQueue =>
|
| | | StaticThreadSafeInit(() => _InitQueue, () =>
|
| | | _InitQueue = new PunisherQueue<QtProjectTracker>());
|
| | | static ConcurrentDictionary<string, QtProjectTracker> Instances => StaticLazy.Get(() =>
|
| | | Instances, () => new ConcurrentDictionary<string, QtProjectTracker>());
|
| | |
|
| | | static IVsTaskStatusCenterService _StatusCenter;
|
| | | static IVsTaskStatusCenterService StatusCenter => StaticThreadSafeInit(() => _StatusCenter,
|
| | | () => _StatusCenter = VsServiceProvider
|
| | | .GetService<SVsTaskStatusCenterService, IVsTaskStatusCenterService>());
|
| | | static PunisherQueue<QtProjectTracker> InitQueue => StaticLazy.Get(() =>
|
| | | InitQueue, () => new PunisherQueue<QtProjectTracker>());
|
| | |
|
| | | static IVsTaskStatusCenterService StatusCenter => StaticLazy.Get(() =>
|
| | | StatusCenter, () => VsServiceProvider
|
| | | .GetService<SVsTaskStatusCenterService, IVsTaskStatusCenterService>());
|
| | |
|
| | | static Task InitDispatcher { get; set; }
|
| | | static ITaskHandler2 InitStatus { get; set; }
|
| | |
| | | Initialized = new EventWaitHandle(false, EventResetMode.ManualReset);
|
| | | }
|
| | |
|
| | | class Subscriber : IDisposable
|
| | | {
|
| | | public Subscriber(QtProjectTracker tracker, ConfiguredProject config)
|
| | | {
|
| | | Tracker = tracker;
|
| | | Config = config;
|
| | | Subscription = Config.Services.ProjectSubscription.JointRuleSource.SourceBlock
|
| | | .LinkTo(new SubscriberAction(ProjectUpdateAsync),
|
| | | ruleNames: new[]
|
| | | {
|
| | | "ClCompile",
|
| | | "QtRule10_Settings",
|
| | | "QtRule30_Moc",
|
| | | "QtRule40_Rcc",
|
| | | "QtRule60_Repc",
|
| | | "QtRule50_Uic",
|
| | | "QtRule_Translation",
|
| | | "QtRule70_Deploy",
|
| | | },
|
| | | initialDataAsNew: false
|
| | | );
|
| | | }
|
| | |
|
| | | QtProjectTracker Tracker { get; set; }
|
| | | ConfiguredProject Config { get; set; }
|
| | | IDisposable Subscription { get; set; }
|
| | |
|
| | | public void Dispose()
|
| | | {
|
| | | Subscription?.Dispose();
|
| | | Subscription = null;
|
| | | }
|
| | |
|
| | | async Task ProjectUpdateAsync(IProjectVersionedValue<IProjectSubscriptionUpdate> update)
|
| | | {
|
| | | await Tracker.OnProjectUpdateAsync(Config, update.Value);
|
| | | }
|
| | | }
|
| | |
|
| | | public EnvDTE.Project Project { get; private set; }
|
| | | public string ProjectPath { get; private set; }
|
| | | public VCProject VcProject { get; private set; }
|
| | | public UnconfiguredProject UnconfiguredProject { get; private set; }
|
| | | public EventWaitHandle Initialized { get; private set; }
|
| | | List<Subscriber> Subscribers { get; set; }
|
| | | public EventWaitHandle Initialized { get; }
|
| | |
|
| | | public static bool IsTracked(EnvDTE.Project project)
|
| | | public static bool IsTracked(string projectPath)
|
| | | {
|
| | | return Instances.ContainsKey(project.FullName);
|
| | | return Instances.ContainsKey(projectPath);
|
| | | }
|
| | |
|
| | | public static void Add(EnvDTE.Project project)
|
| | | {
|
| | | if (!QtVsToolsPackage.Instance.Options.ProjectTracking)
|
| | | return;
|
| | | Get(project);
|
| | |
|
| | | ThreadHelper.ThrowIfNotOnUIThread();
|
| | | Get(project, project.FullName);
|
| | | }
|
| | |
|
| | | public static QtProjectTracker Get(EnvDTE.Project project)
|
| | | public static QtProjectTracker Get(EnvDTE.Project project, string projectPath)
|
| | | {
|
| | | lock (StaticCriticalSection) {
|
| | | QtProjectTracker tracker = null;
|
| | | if (Instances.TryGetValue(project.FullName, out tracker))
|
| | | if (Instances.TryGetValue(projectPath, out QtProjectTracker tracker))
|
| | | return tracker;
|
| | | tracker = new QtProjectTracker
|
| | | {
|
| | | Project = project,
|
| | | ProjectPath = projectPath
|
| | | };
|
| | | Instances[project.FullName] = tracker;
|
| | | Instances[projectPath] = tracker;
|
| | | InitQueue.Enqueue(tracker);
|
| | | if (InitDispatcher == null)
|
| | | InitDispatcher = Task.Run(InitDispatcherLoopAsync);
|
| | |
| | | while (!QtVsToolsPackage.Instance.Zombied) {
|
| | | while (InitQueue.IsEmpty)
|
| | | await Task.Delay(100);
|
| | | QtProjectTracker tracker;
|
| | | if (InitQueue.TryDequeue(out tracker)) {
|
| | | if (InitQueue.TryDequeue(out QtProjectTracker tracker)) {
|
| | | if (InitStatus == null) {
|
| | | await QtVsToolsPackage.Instance.JoinableTaskFactory.SwitchToMainThreadAsync();
|
| | | await QtVsToolsPackage.Instance.JoinableTaskFactory
|
| | | .SwitchToMainThreadAsync();
|
| | | tracker.BeginInitStatus();
|
| | | await TaskScheduler.Default;
|
| | | } else {
|
| | | await QtVsToolsPackage.Instance.JoinableTaskFactory
|
| | | .SwitchToMainThreadAsync();
|
| | | tracker.UpdateInitStatus(0);
|
| | | await TaskScheduler.Default;
|
| | | }
|
| | | await tracker.InitializeAsync();
|
| | | }
|
| | |
| | | async Task InitializeAsync()
|
| | | {
|
| | | int p = 0;
|
| | | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
| | | UpdateInitStatus(p += 10);
|
| | |
|
| | | await QtVsToolsPackage.Instance.JoinableTaskFactory.SwitchToMainThreadAsync();
|
| | | VcProject = Project.Object as VCProject;
|
| | | if (VcProject == null)
|
| | | return;
|
| | | UpdateInitStatus(p += 10);
|
| | |
|
| | | var context = Project.Object as IVsBrowseObjectContext;
|
| | |
| | |
|
| | | Initialized.Set();
|
| | |
|
| | | Subscribers = new List<Subscriber>();
|
| | | int n = configs.Count;
|
| | | int d = (100 - p) / (n * 2);
|
| | | foreach (var config in configs) {
|
| | | var configProject = await UnconfiguredProject.LoadConfiguredProjectAsync(config);
|
| | | UpdateInitStatus(p += d);
|
| | | Subscribers.Add(new Subscriber(this, configProject));
|
| | | configProject.ProjectUnloading += OnProjectUnloading;
|
| | | configProject.ProjectUnloading += OnProjectUnloadingAsync;
|
| | | if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
|
| | | Messages.Print(string.Format(
|
| | | "{0:HH:mm:ss.FFF} QtProjectTracker({1}): Started tracking [{2}] {3}",
|
| | | DateTime.Now, Thread.CurrentThread.ManagedThreadId,
|
| | | config.Name,
|
| | | UnconfiguredProject.FullPath));
|
| | | config.Name, ProjectPath));
|
| | | }
|
| | | UpdateInitStatus(p += d);
|
| | | }
|
| | | }
|
| | |
|
| | |
|
| | | async Task OnProjectUpdateAsync(ConfiguredProject config, IProjectSubscriptionUpdate update)
|
| | | {
|
| | | var changes = update.ProjectChanges.Values
|
| | | .Where(x => x.Difference.AnyChanges)
|
| | | .Select(x => x.Difference);
|
| | | var changesCount = changes
|
| | | .Select(x => x.AddedItems.Count
|
| | | + x.ChangedItems.Count
|
| | | + x.ChangedProperties.Count
|
| | | + x.RemovedItems.Count
|
| | | + x.RenamedItems.Count)
|
| | | .Sum();
|
| | | var changedProps = changes.SelectMany(x => x.ChangedProperties);
|
| | | if (changesCount == 0
|
| | | || (changesCount == 1
|
| | | && changedProps.Count() == 1
|
| | | && changedProps.First() == "QtLastBackgroundBuild")) {
|
| | | return;
|
| | | }
|
| | |
|
| | | if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
|
| | | Messages.Print(string.Format(
|
| | | "{0:HH:mm:ss.FFF} QtProjectTracker({1}): Changed [{2}] {3}",
|
| | | DateTime.Now, Thread.CurrentThread.ManagedThreadId,
|
| | | config.ProjectConfiguration.Name,
|
| | | config.UnconfiguredProject.FullPath));
|
| | | }
|
| | | await QtProjectIntellisense.RefreshAsync(Project, config.ProjectConfiguration.Name);
|
| | | }
|
| | |
|
| | | async Task OnProjectUnloading(object sender, EventArgs args)
|
| | | async Task OnProjectUnloadingAsync(object sender, EventArgs args)
|
| | | {
|
| | | var project = sender as ConfiguredProject;
|
| | | if (project == null || project.Services == null)
|
| | |
| | | project.UnconfiguredProject.FullPath));
|
| | | }
|
| | | lock (CriticalSection) {
|
| | | if (Subscribers != null) {
|
| | | Subscribers.ForEach(s => s.Dispose());
|
| | | Subscribers.Clear();
|
| | | Subscribers = null;
|
| | | }
|
| | | project.ProjectUnloading -= OnProjectUnloading;
|
| | | Instances.TryRemove(Project.FullName, out QtProjectTracker tracker);
|
| | | project.ProjectUnloading -= OnProjectUnloadingAsync;
|
| | | Instances.TryRemove(project.UnconfiguredProject.FullPath, out QtProjectTracker _);
|
| | | }
|
| | | await Task.Yield();
|
| | | }
|
| | |
|
| | | void BeginInitStatus()
|
| | | {
|
| | | ThreadHelper.ThrowIfNotOnUIThread();
|
| | |
|
| | | lock (StaticCriticalSection) {
|
| | | if (InitStatus != null)
|
| | | return;
|
| | |
| | | PercentComplete = 0
|
| | | })
|
| | | as ITaskHandler2;
|
| | | } catch (Exception e) {
|
| | | Messages.Print(
|
| | | e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);
|
| | | } catch (Exception exception) {
|
| | | exception.Log();
|
| | | }
|
| | | InitStatus.RegisterTask(new Task(() => throw new InvalidOperationException()));
|
| | | }
|
| | |
| | | InitStatus.Progress.Report(new TaskProgressData
|
| | | {
|
| | | ProgressText = string.Format("{0} ({1} project(s) remaining)",
|
| | | Project.Name, InitQueue.Count),
|
| | | Path.GetFileNameWithoutExtension(ProjectPath), InitQueue.Count),
|
| | | CanBeCanceled = true,
|
| | | PercentComplete = percentComplete
|
| | | });
|
| | | } catch (Exception e) {
|
| | | Messages.Print(
|
| | | e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);
|
| | | } catch (Exception exception) {
|
| | | exception.Log();
|
| | | }
|
| | | }
|
| | | }
|