diff options
author | Miguel Costa <miguel.costa@qt.io> | 2019-06-17 22:45:04 +0200 |
---|---|---|
committer | Miguel Costa <miguel.costa@qt.io> | 2019-08-16 09:17:42 +0000 |
commit | b8c696c3e0b8368dca281f144b10f4d91b9896c9 (patch) | |
tree | 1e1534fcb1faffa40d868017447d6ed150685e2a /src | |
parent | 76bfe82c530a5f3996342eb01a5fe2fa9a10a3f4 (diff) |
Convert existing projects to V3 format
Previous changes introduced a new project format (V3) that allows Qt
settings to be stored and accessed as MSBuild properties. This change
now adds a conversion from previous format versions to V3.
Task-number: QTVSADDINBUG-575
Change-Id: Ica8bce852010130e5af32f752bd1b3a7ec7d6f93
Reviewed-by: Jörg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/qtprojectlib/MsBuildProject.cs | 287 | ||||
-rw-r--r-- | src/qtprojectlib/ProjectImporter.cs | 3 | ||||
-rw-r--r-- | src/qtvstools/QtMenus.vsct_TT | 1 | ||||
-rw-r--r-- | src/qtvstools/QtMsBuildConverter.cs | 2 | ||||
-rw-r--r-- | src/qtvstools/QtProjectContextMenu.cs | 29 |
5 files changed, 312 insertions, 10 deletions
diff --git a/src/qtprojectlib/MsBuildProject.cs b/src/qtprojectlib/MsBuildProject.cs index 21aa3c94..74c64b05 100644 --- a/src/qtprojectlib/MsBuildProject.cs +++ b/src/qtprojectlib/MsBuildProject.cs @@ -43,6 +43,8 @@ using EnvDTE; namespace QtProjectLib { + using static QtVsTools.SyntaxAnalysis.RegExpr; + public class MsBuildProject { class MsBuildXmlFile @@ -203,6 +205,291 @@ namespace QtProjectLib return true; } + /// <summary> + /// Parser for project configuration conditional expressions of the type: + /// + /// '$(Configuration)|$(Platform)'=='_TOKEN_|_TOKEN_' + /// + /// </summary> + Parser _ConfigCondition; + Parser ConfigCondition + { + get + { + if (_ConfigCondition == null) { + var config = new Token("Configuration", CharWord.Repeat()); + var platform = new Token("Platform", CharWord.Repeat()); + var expr = "'$(Configuration)|$(Platform)'=='" & config & "|" & platform & "'"; + try { + _ConfigCondition = expr.Render(); + } catch { } + } + return _ConfigCondition; + } + } + + const StringComparison IGNORE_CASE = StringComparison.InvariantCultureIgnoreCase; + readonly StringComparer IGNORE_CASE_ = StringComparer.InvariantCultureIgnoreCase; + + /// <summary> + /// Converts project format version to the latest version: + /// * Set latest project version; + /// * Add QtSettings property group; + /// * Set QtInstall property; + /// * Remove hard-coded macros, include paths and libs related to Qt modules. + /// * Set QtModules property; + /// </summary> + /// <returns>true if successful</returns> + public bool UpdateProjectFormatVersion() + { + if (ConfigCondition == null) + return false; + var conditionPrefix = "'$(Configuration)|$(Platform)'=="; + + // Get default Qt dir + string defaultQtDir = null; + var defaultVersionName = QtVersionManager.The().GetDefaultVersion(); + var defaultVersion = QtVersionManager.The().GetVersionInfo(defaultVersionName); + if (defaultVersion != null) + defaultQtDir = defaultVersion.qtDir; + + // Get project global properties + var globals = this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "PropertyGroup") + .Where(x => (string)x.Attribute("Label") == "Globals") + .FirstOrDefault(); + if (globals == null) + return false; + + // Set Qt project format version + var projKeyword = globals.Element(ns + "Keyword"); + if (projKeyword == null) + return false; + projKeyword.SetValue(string.Format("QtVS_v{0}", Resources.qtProjectFormatVersion)); + + // Find import of qt.props + var qtPropsImport = this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "ImportGroup") + .Elements(ns + "Import") + .Where(x => (string)x.Attribute("Project") == @"$(QtMsBuild)\qt.props") + .FirstOrDefault(); + if (qtPropsImport == null) + return false; + + // Get project configurations + var configs = this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "ItemGroup") + .Elements(ns + "ProjectConfiguration"); + + // Create QtSettings property group immediately after import of qt.props + foreach (var config in configs) { + qtPropsImport.Parent.AddAfterSelf( + new XElement(ns + "PropertyGroup", + new XAttribute("Label", "QtSettings"), + new XAttribute("Condition", + string.Format("'$(Configuration)|$(Platform)'=='{0}'", + (string)config.Attribute("Include"))))); + } + + // Get project user properties (old format) + var userProps = this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "ProjectExtensions") + .Elements(ns + "VisualStudio") + .Elements(ns + "UserProperties") + .FirstOrDefault(); + if (userProps == null) + return false; + + // Copy Qt build reference to QtInstall project property + this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "PropertyGroup") + .Where(x => ((string)x.Attribute("Label")) == Resources.projLabelConfiguration) + .ToList() + .ForEach(config => + { + string platform = null; + try { + platform = ConfigCondition + .Parse((string)config.Attribute("Condition")) + .GetValues<string>("Platform") + .FirstOrDefault(); + } catch { } + + if (!string.IsNullOrEmpty(platform)) { + var qtInstallName = string.Format("Qt5Version_x0020_{0}", platform); + var qtInstallValue = (string)userProps.Attribute(qtInstallName); + if (!string.IsNullOrEmpty(qtInstallValue)) + config.Add(new XElement(ns + "QtInstall", qtInstallValue)); + } + }); + + // Get C++ compiler properties + var propsCl = this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "ItemDefinitionGroup") + .Where(x => ((string)x.Attribute("Condition")).StartsWith(conditionPrefix)) + .Elements(ns + "ClCompile") + .Select(x => new + { + Self = x, + Config = ((string)x.Parent.Attribute("Condition")) + .Substring(conditionPrefix.Length), + }); + + // Get C++ compiler include directory properties + var propsClIncludePaths = propsCl + .Select(x => new + { + Self = x.Self.Element(ns + "AdditionalIncludeDirectories"), + x.Config + }) + .SelectMany(x => ((string)x.Self).Split(';') + .Select(y => new { x.Config, x.Self, Value = y })); + + // Get C++ compiler macro properties + var propsClDefines = propsCl + .Select(x => new + { + x.Config, + Self = x.Self.Element(ns + "PreprocessorDefinitions") + }) + .SelectMany(x => ((string)x.Self).Split(';') + .Select(y => new { x.Config, x.Self, Value = y })); + + // Get linker properties + var propsLink = this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "ItemDefinitionGroup") + .Where(x => ((string)x.Attribute("Condition")).StartsWith(conditionPrefix)) + .Elements(ns + "Link") + .Select(x => new + { + Self = x, + Config = ((string)x.Parent.Attribute("Condition")) + .Substring(conditionPrefix.Length), + }); + + // Get linker library properties + var propsLinkLibs = propsLink + .Select(x => new + { + x.Config, + Self = x.Self.Element(ns + "AdditionalDependencies") + }) + .SelectMany(x => ((string)x.Self).Split(';') + .Select(y => new { x.Config, x.Self, Value = y })); + + // Extract references to link libraries, Release configuration only + var propsLinkLibsRelease = propsLinkLibs + .Where(x => x.Config.StartsWith("'Release|")); + if (!propsLinkLibsRelease.Any()) + return false; + + // Qt module names, to copy to QtModules property + var moduleNames = new HashSet<string>(); + + // Qt module macros, to remove from compiler macros property + var moduleDefines = new HashSet<string>(); + + // Qt module includes, to remove from compiler include directories property + var moduleIncludePaths = new HashSet<string>(); + + // Qt module link libraries, to remove from liker dependencies property + var moduleLibs = new HashSet<string>(); + + // Go through all known Qt modules and check which ones are currently being used + foreach (var module in QtModules.Instance.GetAvailableModuleInformation()) { + var libPrefix = module.LibraryPrefix; + if (libPrefix.StartsWith("Qt", StringComparison.Ordinal)) + libPrefix = "Qt5" + libPrefix.Substring(2); + var libRelease = libPrefix + ".lib"; + var libDebug = libPrefix + "d.lib"; + + // Check if module is used ::= module lib is present in release link dependencies + var isModuleUsed = propsLinkLibsRelease.Where(x => x.Value + .EndsWith(libRelease, IGNORE_CASE)) + .Any(); + + if (isModuleUsed) { + + // Qt module names, to copy to QtModules property + if (!string.IsNullOrEmpty(module.proVarQT)) + moduleNames.UnionWith(module.proVarQT.Split(' ')); + + // Qt module macros, to remove from compiler macros property + moduleDefines.UnionWith(module.Defines); + + // Qt module includes, to remove from compiler include directories property + moduleIncludePaths.UnionWith( + module.IncludePath.Select(x => Path.GetFileName(x))); + + // Qt module link libraries, to remove from liker dependencies property + moduleLibs.UnionWith( + module.AdditionalLibraries.Select(x => Path.GetFileName(x))); + moduleLibs.UnionWith( + module.AdditionalLibrariesDebug.Select(x => Path.GetFileName(x))); + moduleLibs.Add(libRelease); + moduleLibs.Add(libDebug); + } + } + + // Remove Qt module macros from compiler properties + propsClDefines.GroupBy(x => x.Self).Select(x => new + { + Tag = x.Key, + Value = string.Join(";", x.Select(y => y.Value) + .Where(y => !moduleDefines.Contains(y))) + }) + .ToList() + .ForEach(x => x.Tag.SetValue(x.Value)); + + // Remove Qt module include paths from compiler properties + propsClIncludePaths.GroupBy(x => x.Self).Select(x => new + { + Tag = x.Key, + Value = string.Join(";", x.Select(y => y.Value).Where(y => + // Exclude include paths of Qt modules + !moduleIncludePaths.Contains(Path.GetFileName(y), IGNORE_CASE_) + || ( + // Exclude paths rooted on $(QTDIR) + !y.StartsWith("$(QTDIR)", IGNORE_CASE) + && ( + // Exclude paths rooted on the default Qt dir + string.IsNullOrEmpty(defaultQtDir) + || !y.StartsWith(defaultQtDir, IGNORE_CASE) + ) + ) + )) + }) + .ToList() + .ForEach(x => x.Tag.SetValue(x.Value)); + + // Remove Qt module libraries from linker properties + propsLinkLibs.GroupBy(x => x.Self).Select(x => new + { + Tag = x.Key, + Value = string.Join(";", x.Select(y => y.Value) + .Where(y => !moduleLibs.Contains(Path.GetFileName(y), IGNORE_CASE_))) + }) + .ToList() + .ForEach(x => x.Tag.SetValue(x.Value)); + + // Add Qt module names to QtModules project property + this[Files.Project].xml + .Elements(ns + "Project") + .Elements(ns + "PropertyGroup") + .Where(x => ((string)x.Attribute("Label")) == Resources.projLabelQtSettings) + .ToList() + .ForEach(x => x.Add(new XElement(ns + "QtModules", string.Join(";", moduleNames)))); + + return true; + } + public bool SetDefaultWindowsSDKVersion(string winSDKVersion) { var xGlobals = this[Files.Project].xml diff --git a/src/qtprojectlib/ProjectImporter.cs b/src/qtprojectlib/ProjectImporter.cs index 36903b6d..9f56c34a 100644 --- a/src/qtprojectlib/ProjectImporter.cs +++ b/src/qtprojectlib/ProjectImporter.cs @@ -229,6 +229,9 @@ namespace QtProjectLib ok = xmlProject.SetDefaultWindowsSDKVersion(versionWin10SDK); } #endif + if (ok) + ok = xmlProject.UpdateProjectFormatVersion(); + if (!ok) { Messages.PaneMessage(dteObject, SR.GetString("ImportProject_CannotConvertProject", projectFile.Name)); diff --git a/src/qtvstools/QtMenus.vsct_TT b/src/qtvstools/QtMenus.vsct_TT index 69ce3cad..309a44fb 100644 --- a/src/qtvstools/QtMenus.vsct_TT +++ b/src/qtvstools/QtMenus.vsct_TT @@ -437,6 +437,7 @@ <CommandFlag>DefaultDisabled</CommandFlag> <CommandFlag>DefaultInvisible</CommandFlag> <CommandFlag>DynamicVisibility</CommandFlag> + <CommandFlag>TextChanges</CommandFlag> <Strings> <ButtonText>Convert custom build steps to Qt/MSBuild</ButtonText> </Strings> diff --git a/src/qtvstools/QtMsBuildConverter.cs b/src/qtvstools/QtMsBuildConverter.cs index 24cb3661..759c9ef1 100644 --- a/src/qtvstools/QtMsBuildConverter.cs +++ b/src/qtvstools/QtMsBuildConverter.cs @@ -131,6 +131,8 @@ namespace QtVsTools if (ok) ok = xmlProject.EnableMultiProcessorCompilation(); if (ok) + ok = xmlProject.UpdateProjectFormatVersion(); + if (ok) ok = xmlProject.Save(); return ok; } diff --git a/src/qtvstools/QtProjectContextMenu.cs b/src/qtvstools/QtProjectContextMenu.cs index b9ba52bf..cd287a4a 100644 --- a/src/qtvstools/QtProjectContextMenu.cs +++ b/src/qtvstools/QtProjectContextMenu.cs @@ -231,6 +231,9 @@ namespace QtVsTools return; var project = HelperFunctions.GetSelectedProject(Vsix.Instance.Dte); + var isQtProject = HelperFunctions.IsQtProject(project); + var isQMakeProject = HelperFunctions.IsQMakeProject(project); + var isQtMsBuildEnabled = QtProject.IsQtMsBuildEnabled(project); switch ((CommandId) command.CommandID.ID) { case CommandId.ImportPriFileProjectId: @@ -249,9 +252,9 @@ namespace QtVsTools { var status = vsCommandStatus.vsCommandStatusSupported; if (project != null) { - if (HelperFunctions.IsQtProject(project)) + if (isQtProject) status |= vsCommandStatus.vsCommandStatusEnabled; - else if (HelperFunctions.IsQMakeProject(project)) + else if (isQMakeProject) status |= vsCommandStatus.vsCommandStatusInvisible; } command.Enabled = ((status & vsCommandStatus.vsCommandStatusEnabled) != 0); @@ -262,9 +265,9 @@ namespace QtVsTools case CommandId.ChangeProjectQtVersionProjectId: { var status = vsCommandStatus.vsCommandStatusSupported; - if ((project == null) || HelperFunctions.IsQtProject(project)) + if ((project == null) || isQtProject) status |= vsCommandStatus.vsCommandStatusInvisible; - else if (HelperFunctions.IsQMakeProject(project)) + else if (isQMakeProject) status |= vsCommandStatus.vsCommandStatusEnabled; else status |= vsCommandStatus.vsCommandStatusInvisible; @@ -274,12 +277,10 @@ namespace QtVsTools break; case CommandId.ProjectConvertToQtMsBuild: { - if (project == null - || (!HelperFunctions.IsQtProject(project) - && !HelperFunctions.IsQMakeProject(project))) { + if (project == null || (!isQtProject && !isQMakeProject)) { command.Visible = false; command.Enabled = false; - } else if (QtProject.IsQtMsBuildEnabled(project)) { + } else if (isQtMsBuildEnabled) { command.Visible = true; command.Enabled = false; } else { @@ -290,16 +291,24 @@ namespace QtVsTools break; } - if (project != null) { + if (project != null && isQtProject) { int projectVersion = QtProject.GetFormatVersion(project); int minProjectVersion = Resources.qtMinFormatVersion_Settings; switch ((CommandId)command.CommandID.ID) { case CommandId.QtProjectSettingsProjectId: case CommandId.ChangeProjectQtVersionProjectId: - case CommandId.ProjectConvertToQtMsBuild: if (projectVersion >= minProjectVersion) command.Visible = command.Enabled = false; break; + case CommandId.ProjectConvertToQtMsBuild: + if (projectVersion >= minProjectVersion) { + command.Visible = command.Enabled = false; + } else { + command.Visible = command.Enabled = true; + if (isQtMsBuildEnabled) + command.Text = "Upgrade to latest Qt project format version"; + } + break; } } } |