/****************************************************************************
|
**
|
** 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.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.TaskStatusCenter;
|
using Microsoft.VisualStudio.Threading;
|
using EnvDTE;
|
|
namespace QtVsTools.QtMsBuild
|
{
|
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 PunisherQueue<QtProjectTracker> _InitQueue;
|
static PunisherQueue<QtProjectTracker> InitQueue =>
|
StaticThreadSafeInit(() => _InitQueue, () =>
|
_InitQueue = new PunisherQueue<QtProjectTracker>());
|
|
static IVsTaskStatusCenterService _StatusCenter;
|
static IVsTaskStatusCenterService StatusCenter => StaticThreadSafeInit(() => _StatusCenter,
|
() => _StatusCenter = VsServiceProvider
|
.GetService<SVsTaskStatusCenterService, IVsTaskStatusCenterService>());
|
|
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);
|
}
|
|
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 UnconfiguredProject UnconfiguredProject { get; private set; }
|
public EventWaitHandle Initialized { get; private set; }
|
List<Subscriber> Subscribers { get; set; }
|
|
public static bool IsTracked(EnvDTE.Project project)
|
{
|
return Instances.ContainsKey(project.FullName);
|
}
|
|
public static void Add(EnvDTE.Project project)
|
{
|
if (!QtVsToolsPackage.Instance.Options.ProjectTracking)
|
return;
|
Get(project);
|
}
|
|
public static QtProjectTracker Get(EnvDTE.Project project)
|
{
|
lock (StaticCriticalSection) {
|
QtProjectTracker tracker = null;
|
if (Instances.TryGetValue(project.FullName, out tracker))
|
return tracker;
|
tracker = new QtProjectTracker
|
{
|
Project = project,
|
};
|
Instances[project.FullName] = 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);
|
QtProjectTracker tracker;
|
if (InitQueue.TryDequeue(out tracker)) {
|
if (InitStatus == null) {
|
await QtVsToolsPackage.Instance.JoinableTaskFactory.SwitchToMainThreadAsync();
|
tracker.BeginInitStatus();
|
await TaskScheduler.Default;
|
} else {
|
tracker.UpdateInitStatus(0);
|
}
|
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;
|
UpdateInitStatus(p += 10);
|
|
await QtVsToolsPackage.Instance.JoinableTaskFactory.SwitchToMainThreadAsync();
|
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();
|
|
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;
|
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));
|
}
|
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)
|
{
|
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) {
|
if (Subscribers != null) {
|
Subscribers.ForEach(s => s.Dispose());
|
Subscribers.Clear();
|
Subscribers = null;
|
}
|
project.ProjectUnloading -= OnProjectUnloading;
|
Instances.TryRemove(Project.FullName, out QtProjectTracker tracker);
|
}
|
}
|
|
void BeginInitStatus()
|
{
|
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 e) {
|
Messages.Print(
|
e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);
|
}
|
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)",
|
Project.Name, InitQueue.Count),
|
CanBeCanceled = true,
|
PercentComplete = percentComplete
|
});
|
} catch (Exception e) {
|
Messages.Print(
|
e.Message + "\r\n\r\nStacktrace:\r\n" + e.StackTrace);
|
}
|
}
|
}
|
|
void EndInitStatus()
|
{
|
lock (StaticCriticalSection) {
|
if (InitStatus == null)
|
return;
|
InitStatus.Dismiss();
|
InitStatus = null;
|
}
|
}
|
}
|
}
|