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/qtprojectlib/MsBuildProject.cs | |
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/qtprojectlib/MsBuildProject.cs')
-rw-r--r-- | src/qtprojectlib/MsBuildProject.cs | 287 |
1 files changed, 287 insertions, 0 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 |