/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt VS Tools. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ 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.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.ProjectSystem.Properties; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.TaskStatusCenter; using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.VCProjectEngine; using Task = System.Threading.Tasks.Task; namespace QtVsTools.QtMsBuild { using Common; using Core; using VisualStudio; using SubscriberAction = ActionBlock>; class QtProjectTracker : Concurrent { static LazyFactory StaticLazy { get; } = new LazyFactory(); static ConcurrentDictionary Instances => StaticLazy.Get(() => Instances, () => new ConcurrentDictionary()); static PunisherQueue InitQueue => StaticLazy.Get(() => InitQueue, () => new PunisherQueue()); static IVsTaskStatusCenterService StatusCenter => StaticLazy.Get(() => StatusCenter, () => VsServiceProvider .GetService()); static Task InitDispatcher { get; set; } static ITaskHandler2 InitStatus { get; set; } public static string SolutionPath { get; set; } = string.Empty; private QtProjectTracker() { Initialized = new EventWaitHandle(false, EventResetMode.ManualReset); } 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; } public static bool IsTracked(string projectPath) { return Instances.ContainsKey(projectPath); } public static void Add(EnvDTE.Project project) { if (!QtVsToolsPackage.Instance.Options.ProjectTracking) return; ThreadHelper.ThrowIfNotOnUIThread(); Get(project, project.FullName); } public static QtProjectTracker Get(EnvDTE.Project project, string projectPath) { lock (StaticCriticalSection) { if (Instances.TryGetValue(projectPath, out QtProjectTracker tracker)) return tracker; tracker = new QtProjectTracker { Project = project, ProjectPath = projectPath }; Instances[projectPath] = tracker; InitQueue.Enqueue(tracker); if (InitDispatcher == null) InitDispatcher = Task.Run(InitDispatcherLoopAsync); return tracker; } } public static void Reset() { lock (StaticCriticalSection) { Instances.Clear(); InitQueue.Clear(); } } static async Task InitDispatcherLoopAsync() { while (!QtVsToolsPackage.Instance.Zombied) { while (InitQueue.IsEmpty) await Task.Delay(100); if (InitQueue.TryDequeue(out QtProjectTracker tracker)) { if (InitStatus == null) { 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(); } if (InitStatus != null && (InitQueue.IsEmpty || InitStatus.UserCancellation.IsCancellationRequested)) { if (InitStatus.UserCancellation.IsCancellationRequested) { InitQueue.Clear(); } tracker.EndInitStatus(); } } } async Task InitializeAsync() { int p = 0; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); UpdateInitStatus(p += 10); VcProject = Project.Object as VCProject; if (VcProject == null) return; UpdateInitStatus(p += 10); var context = Project.Object as IVsBrowseObjectContext; if (context == null) return; UpdateInitStatus(p += 10); UnconfiguredProject = context.UnconfiguredProject; if (UnconfiguredProject == null || UnconfiguredProject.ProjectService == null || UnconfiguredProject.ProjectService.Services == null) return; await TaskScheduler.Default; UpdateInitStatus(p += 10); var configs = await UnconfiguredProject.Services .ProjectConfigurationsService.GetKnownProjectConfigurationsAsync(); UpdateInitStatus(p += 10); Initialized.Set(); int n = configs.Count; int d = (100 - p) / (n * 2); foreach (var config in configs) { var configProject = await UnconfiguredProject.LoadConfiguredProjectAsync(config); UpdateInitStatus(p += d); 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, ProjectPath)); } UpdateInitStatus(p += d); } } async Task OnProjectUnloadingAsync(object sender, EventArgs args) { var project = sender as ConfiguredProject; if (project == null || project.Services == null) return; if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) { Messages.Print(string.Format( "{0:HH:mm:ss.FFF} QtProjectTracker: Stopped tracking [{1}] {2}", DateTime.Now, project.ProjectConfiguration.Name, project.UnconfiguredProject.FullPath)); } lock (CriticalSection) { project.ProjectUnloading -= OnProjectUnloadingAsync; Instances.TryRemove(project.UnconfiguredProject.FullPath, out QtProjectTracker _); } await Task.Yield(); } void BeginInitStatus() { ThreadHelper.ThrowIfNotOnUIThread(); lock (StaticCriticalSection) { if (InitStatus != null) return; try { InitStatus = StatusCenter.PreRegister( new TaskHandlerOptions { Title = "Qt VS Tools: Setting up project tracking..." }, new TaskProgressData { ProgressText = string.Format("{0} ({1} projects remaining)", Project.Name, InitQueue.Count), CanBeCanceled = true, PercentComplete = 0 }) as ITaskHandler2; } catch (Exception exception) { exception.Log(); } InitStatus.RegisterTask(new Task(() => throw new InvalidOperationException())); } } void UpdateInitStatus(int percentComplete) { lock (StaticCriticalSection) { if (InitStatus == null) return; try { InitStatus.Progress.Report(new TaskProgressData { ProgressText = string.Format("{0} ({1} project(s) remaining)", Path.GetFileNameWithoutExtension(ProjectPath), InitQueue.Count), CanBeCanceled = true, PercentComplete = percentComplete }); } catch (Exception exception) { exception.Log(); } } } void EndInitStatus() { lock (StaticCriticalSection) { if (InitStatus == null) return; InitStatus.Dismiss(); InitStatus = null; } } } }