From fd9d180dd2f434eab5520da7a2eb3821479ecf63 Mon Sep 17 00:00:00 2001 From: Miguel Costa Date: Tue, 25 Jun 2019 10:46:41 +0200 Subject: Add support for VS design-time build This change adds support for VS project build and property evaluation during design time. This will have an impact on the availability and accuracy of build-time information in property pages (e.g. compiler switches) and of syntax highlighting/code model in the VS editor. Change-Id: Ia05f5a14a43d0c76cb2eea2f7b0d3d519acafbe6 Reviewed-by: Oliver Wolff --- src/qtmsbuild/qt.props | 10 ++ src/qtmsbuild/qt_globals.targets | 101 +++++++---- src/qtmsbuild/qt_vars.targets | 23 ++- src/qtvstools/DteEventsHandler.cs | 5 +- .../QtMsBuild/Components/QtVarsDesignTime.cs | 184 +++++++++++++++++++++ src/qtvstools/QtVsTools.csproj | 5 + 6 files changed, 297 insertions(+), 31 deletions(-) create mode 100644 src/qtvstools/QtMsBuild/Components/QtVarsDesignTime.cs diff --git a/src/qtmsbuild/qt.props b/src/qtmsbuild/qt.props index 08f42d6c..90ef73ee 100644 --- a/src/qtmsbuild/qt.props +++ b/src/qtmsbuild/qt.props @@ -105,12 +105,22 @@ >$(QtVarsWorkDir)$(QtVarsFileName) $(QtVarsOutputDir)$(QtVarsFileName) + $(QtVarsOutputDir)$(QtVarsFileNameWithoutExt).designtime.idx + $([System.IO.File]::ReadAllText('$(QtVarsIndexPathDesignTime)')) + + + diff --git a/src/qtmsbuild/qt_globals.targets b/src/qtmsbuild/qt_globals.targets index e3be06f8..90154d6f 100644 --- a/src/qtmsbuild/qt_globals.targets +++ b/src/qtmsbuild/qt_globals.targets @@ -41,8 +41,27 @@ // Build dependencies // --> - QtVersion;$(BuildDependsOn);Qt - $(CleanDependsOn);QtClean + + QtVersion; + $(BuildDependsOn); + Qt + + + $(CleanDependsOn); + QtClean + + + $(DesignTimeBuildInitTargets); + Qt + + + $(ComputeCompileInputsTargets); + QtWork + + + $(ComputeLinkInputsTargets); + QtWork + - + @@ -113,7 +134,8 @@ // Analyze work request and decide if the Qt tool needs to be called or if the output from the // previous call is still valid. // --> - @@ -137,6 +159,9 @@ @(QtWork->'%(WorkType)(%(Identity))') @(QtWork->'%(DependenciesChanged)') @(QtWork->'%(InputChanged)') + true - + + true + true + + + + 0 true @@ -165,22 +205,16 @@ - + $(work_hash) - + Condition="'$(QtSkipWork)' != 'true'" + DependsOnTargets="QtPrepare;QtWorkPrepare"> @@ -216,7 +252,10 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Run work in parallel processes // --> - @@ -225,7 +264,10 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Run work in a single process // --> - @@ -234,7 +276,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Save tracking log of files read during build; used by VS to check the up-to-date status // --> - + %(QtWorkResult.WorkType) @@ -257,7 +299,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Save tracking log of files written during build; used by VS to check the up-to-date status // --> - + %(QtWorkResult.WorkType) @@ -278,7 +320,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Log output files; this is used by VS to determine what files to delete on "Clean" // --> - + @(QtWorkResult, '|') @@ -294,7 +336,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Log calls to Qt tools; used in QtWorkPrepare to detect changes to the options of Qt tools // --> - @@ -303,7 +345,10 @@ /////////////////////////////////////////////////////////////////////////////////////////////// // Generate build error if a Qt tool did not terminate correctly // --> - diff --git a/src/qtmsbuild/qt_vars.targets b/src/qtmsbuild/qt_vars.targets index 0d4cf834..f58525c5 100644 --- a/src/qtmsbuild/qt_vars.targets +++ b/src/qtmsbuild/qt_vars.targets @@ -35,7 +35,7 @@ - QtVars;$(QtDependsOn) + QtVars;$(QtDependsOn) @@ -245,7 +245,7 @@ for (varName, $$list($$sorted(varNames))) { Condition="'$(QtVarsDebug)' == 'true'" Text="@(QMakeError->'%(Identity)','%0D%0A')"/> @@ -284,6 +284,24 @@ for (varName, $$list($$sorted(varNames))) { Condition="'$(QtVarsDebug)' != 'true'" Directories="$(QtVarsWorkDir)"/> + + + $([System.IO.Path]::Combine('$(QtVarsOutputDir)', + 'qtvars_$([System.IO.Path]::GetRandomFileName()).designtime.props')) + + + + + @@ -294,6 +312,7 @@ for (varName, $$list($$sorted(varNames))) { diff --git a/src/qtvstools/DteEventsHandler.cs b/src/qtvstools/DteEventsHandler.cs index 516bc585..7ec13f4c 100644 --- a/src/qtvstools/DteEventsHandler.cs +++ b/src/qtvstools/DteEventsHandler.cs @@ -33,6 +33,7 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.VCProjectEngine; using QtProjectLib; using QtProjectLib.QtMsBuild; +using QtVsTools.QtMsBuild; using System; using System.IO; using System.Linq; @@ -542,8 +543,10 @@ namespace QtVsTools void SolutionEvents_Opened() { foreach (var p in HelperFunctions.ProjectsInSolution(Vsix.Instance.Dte)) { - if (HelperFunctions.IsQtProject(p)) + if (HelperFunctions.IsQtProject(p)) { InitializeVCProject(p); + QtVarsDesignTime.AddProject(p); + } } } diff --git a/src/qtvstools/QtMsBuild/Components/QtVarsDesignTime.cs b/src/qtvstools/QtMsBuild/Components/QtVarsDesignTime.cs new file mode 100644 index 00000000..85dcd399 --- /dev/null +++ b/src/qtvstools/QtMsBuild/Components/QtVarsDesignTime.cs @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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.Collections.Immutable; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Properties; +using EnvDTE; +using QtProjectLib; +using System.Diagnostics; + +namespace QtVsTools.QtMsBuild +{ + class QtVarsDesignTime + { + static ConcurrentDictionary Instances { get; set; } + + EnvDTE.Project Project { get; set; } + + UnconfiguredProject UnconfiguredProject { get; set; } + IEnumerable ConfiguredProjects { get; set; } + IProjectLockService LockService { get; set; } + + static readonly object criticalSection = new object(); + public static void AddProject(EnvDTE.Project project) + { + lock (criticalSection) { + if (Instances == null) + Instances = new ConcurrentDictionary(); + } + Instances[project] = new QtVarsDesignTime(project); + } + + private QtVarsDesignTime(EnvDTE.Project project) + { + Project = project; + Task.Run(Initialize).Wait(5000); + } + + bool initialized = false; + + private async Task Initialize() + { + var context = Project.Object as IVsBrowseObjectContext; + if (context == null) + return; + + UnconfiguredProject = context.UnconfiguredProject; + if (UnconfiguredProject == null + || UnconfiguredProject.ProjectService == null + || UnconfiguredProject.ProjectService.Services == null) + return; + + LockService = UnconfiguredProject.ProjectService.Services.ProjectLockService; + if (LockService == null) + return; + + var configs = await UnconfiguredProject.Services + .ProjectConfigurationsService.GetKnownProjectConfigurationsAsync(); + + initialized = true; + + foreach (var config in configs) { + var configProject = await UnconfiguredProject.LoadConfiguredProjectAsync(config); + configProject.ProjectChanged += OnProjectChanged; + configProject.ProjectUnloading += OnProjectUnloading; + await DesignTimeUpdateQtVarsAsync(configProject); + } + } + + private void OnProjectChanged(object sender, EventArgs e) + { + if (!initialized) + return; + + var project = sender as ConfiguredProject; + if (project == null || project.Services == null) + return; + + Task.Run(async () => await DesignTimeUpdateQtVarsAsync(project)); + } + + private async Task DesignTimeUpdateQtVarsAsync(ConfiguredProject project) + { + if (project == null) + return; + + try { + ProjectWriteLockReleaser writeAccess; + var timer = Stopwatch.StartNew(); + while (timer.IsRunning) { + try { + writeAccess = await LockService.WriteLockAsync(); + timer.Stop(); + } catch (InvalidOperationException) { + if (timer.ElapsedMilliseconds >= 5000) + throw; + using (var readAccess = await LockService.ReadLockAsync()) + await readAccess.ReleaseAsync(); + } + } + + using (writeAccess) + using (var buildManager = new BuildManager()) { + var msBuildProject = await writeAccess.GetProjectAsync(project); + + var configProps = project.ProjectConfiguration + .Dimensions.ToImmutableDictionary(); + + var projectInstance = new ProjectInstance(msBuildProject.Xml, + new Dictionary(configProps) + { { "DesignTimeBuild", "true" } }, + null, new ProjectCollection()); + + var buildRequest = new BuildRequestData(projectInstance, + targetsToBuild: new[] { "QtVars" }, + hostServices: null, + flags: BuildRequestDataFlags.ProvideProjectStateAfterBuild); + + var result = buildManager.Build(new BuildParameters(), buildRequest); + + if (result == null + || result.ResultsByTarget == null + || result.OverallResult != BuildResultCode.Success) { + Messages.PaneMessageSafe(Vsix.Instance.Dte, timeout: 5000, + str: string.Format("{0}: design-time pre-build FAILED!", + Path.GetFileName(UnconfiguredProject.FullPath))); + } else { + var resultQtVars = result.ResultsByTarget["QtVars"]; + if (resultQtVars.ResultCode == TargetResultCode.Success) { + msBuildProject.MarkDirty(); + } + } + } + } catch (Exception e) { + Messages.PaneMessageSafe(Vsix.Instance.Dte, timeout: 5000, + str: string.Format("{0}: design-time pre-build ERROR: {1}", + Path.GetFileName(UnconfiguredProject.FullPath), e.Message)); + } + } + + private async Task OnProjectUnloading(object sender, EventArgs args) + { + var project = sender as ConfiguredProject; + if (project == null || project.Services == null) + return; + project.ProjectChanged -= OnProjectChanged; + project.ProjectUnloading -= OnProjectUnloading; + Instances[Project] = null; + await Task.Yield(); + } + } +} diff --git a/src/qtvstools/QtVsTools.csproj b/src/qtvstools/QtVsTools.csproj index 2134d88a..7418b515 100644 --- a/src/qtvstools/QtVsTools.csproj +++ b/src/qtvstools/QtVsTools.csproj @@ -153,6 +153,7 @@ + @@ -462,17 +463,21 @@ + + $(VsInstallRoot)\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Composition.dll $(VsInstallRoot)\Common7\IDE\CommonExtensions\Microsoft\Project\Microsoft.VisualStudio.ProjectSystem.dll + ..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net45\System.Data.SQLite.dll True + -- cgit v1.2.3