aboutsummaryrefslogtreecommitdiffstats
path: root/src/qtvstools.core/MsBuildProject.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/qtvstools.core/MsBuildProject.cs')
-rw-r--r--src/qtvstools.core/MsBuildProject.cs1801
1 files changed, 1801 insertions, 0 deletions
diff --git a/src/qtvstools.core/MsBuildProject.cs b/src/qtvstools.core/MsBuildProject.cs
new file mode 100644
index 00000000..e8b62c46
--- /dev/null
+++ b/src/qtvstools.core/MsBuildProject.cs
@@ -0,0 +1,1801 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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.IO;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using QtVsTools.Core.QtMsBuild;
+using System.Text.RegularExpressions;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Evaluation;
+using QtVsTools.VisualStudio;
+using QtVsTools.SyntaxAnalysis;
+using EnvDTE;
+
+namespace QtVsTools.Core
+{
+ using static HelperFunctions;
+ using static RegExpr;
+
+ public class MsBuildProject
+ {
+ class MsBuildXmlFile
+ {
+ public string filePath = "";
+ public XDocument xml = null;
+ public bool isDirty = false;
+ public XDocument xmlCommitted = null;
+ public bool isCommittedDirty = false;
+ }
+
+ enum Files
+ {
+ Project = 0,
+ Filters,
+ User,
+ Count
+ }
+ MsBuildXmlFile[] files = new MsBuildXmlFile[(int)Files.Count];
+
+ MsBuildProject()
+ {
+ for (int i = 0; i < files.Length; i++)
+ files[i] = new MsBuildXmlFile();
+ }
+
+ MsBuildXmlFile this[Files file]
+ {
+ get
+ {
+ if ((int)file >= (int)Files.Count)
+ return files[0];
+ return files[(int)file];
+ }
+ }
+
+ public string ProjectXml
+ {
+ get
+ {
+ var xml = this[Files.Project].xml;
+ if (xml == null)
+ return "";
+ return xml.ToString(SaveOptions.None);
+ }
+ }
+
+ private static XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
+
+ public static MsBuildProject Load(string pathToProject)
+ {
+ if (!File.Exists(pathToProject))
+ return null;
+
+ MsBuildProject project = new MsBuildProject();
+
+ project[Files.Project].filePath = pathToProject;
+ if (!LoadXml(project[Files.Project]))
+ return null;
+
+ project[Files.Filters].filePath = pathToProject + ".filters";
+ if (File.Exists(project[Files.Filters].filePath) && !LoadXml(project[Files.Filters]))
+ return null;
+
+ project[Files.User].filePath = pathToProject + ".user";
+ if (File.Exists(project[Files.User].filePath) && !LoadXml(project[Files.User]))
+ return null;
+
+ return project;
+ }
+
+ static bool LoadXml(MsBuildXmlFile xmlFile)
+ {
+ try {
+ var xmlText = File.ReadAllText(xmlFile.filePath, Encoding.UTF8);
+ using (var reader = XmlReader.Create(new StringReader(xmlText))) {
+ xmlFile.xml = XDocument.Load(reader, LoadOptions.SetLineInfo);
+ }
+ } catch (Exception) {
+ return false;
+ }
+ xmlFile.xmlCommitted = new XDocument(xmlFile.xml);
+ return true;
+ }
+
+ public bool Save()
+ {
+ foreach (var file in files) {
+ if (file.isDirty) {
+ file.xml?.Save(file.filePath, SaveOptions.None);
+ file.isCommittedDirty = file.isDirty = false;
+ }
+ }
+ return true;
+ }
+
+ void Commit()
+ {
+ foreach (var file in files.Where(x => x.xml != null)) {
+ if (file.isDirty) {
+ //file was modified: sync committed copy
+ file.xmlCommitted = new XDocument(file.xml);
+ file.isCommittedDirty = true;
+ } else {
+ //fail-safe: ensure non-dirty files are in sync with committed copy
+ file.xml = new XDocument(file.xmlCommitted);
+ file.isDirty = file.isCommittedDirty;
+ }
+ }
+ }
+
+ void Rollback()
+ {
+ foreach (var file in files.Where(x => x.xml != null)) {
+ file.xml = new XDocument(file.xmlCommitted);
+ file.isDirty = file.isCommittedDirty;
+ }
+ }
+
+ public string GetProperty(string property_name)
+ {
+ var xProperty = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Elements()
+ .Where(x => x.Name.LocalName == property_name)
+ .FirstOrDefault();
+ if (xProperty == null)
+ return string.Empty;
+ return xProperty.Value;
+ }
+
+ public string GetProperty(string item_type, string property_name)
+ {
+ var xProperty = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Elements(ns + item_type)
+ .Elements()
+ .Where(x => x.Name.LocalName == property_name)
+ .FirstOrDefault();
+ if (xProperty == null)
+ return string.Empty;
+ return xProperty.Value;
+ }
+
+ public IEnumerable<string> GetItems(string item_type)
+ {
+ return this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + item_type)
+ .Select((XElement x) => (string)x.Attribute("Include"));
+ }
+
+ public bool EnableMultiProcessorCompilation()
+ {
+ var xClCompileDefs = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Elements(ns + "ClCompile");
+ foreach (var xClCompileDef in xClCompileDefs)
+ xClCompileDef.Add(new XElement(ns + "MultiProcessorCompilation", "true"));
+
+ this[Files.Project].isDirty = true;
+ Commit();
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Parser for project format version string:
+ ///
+ /// QtVS_vNNN
+ ///
+ /// </summary>
+ Parser _ProjectFormatVersion;
+ Parser ProjectFormatVersion
+ {
+ get
+ {
+ if (_ProjectFormatVersion == null) {
+ var expr = "QtVS_v" & new Token("VERSION", Char['0', '9'].Repeat(3))
+ {
+ new Rule<int> { Capture(value => int.Parse(value)) }
+ };
+ try {
+ _ProjectFormatVersion = expr.Render();
+ } catch { }
+ }
+ return _ProjectFormatVersion;
+ }
+ }
+
+ int? ParseProjectFormatVersion(string text)
+ {
+ if (ProjectFormatVersion == null)
+ return null;
+ try {
+ return ProjectFormatVersion.Parse(text)
+ .GetValues<int>("VERSION")
+ .First();
+ } catch {
+ return null;
+ }
+ }
+
+ 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;
+
+ // 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 configurations
+ var configs = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "ProjectConfiguration");
+
+ // 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;
+
+ // Get project configuration properties
+ var configProps = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Where(pg =>
+ (string)pg.Attribute("Label") == "Configuration"
+ && pg.Attribute("Condition") != null)
+ .ToDictionary(pg => (string)pg.Attribute("Condition"));
+
+ // Set Qt project format version
+ var projKeyword = globals
+ .Elements(ns + "Keyword")
+ .Where(x => x.Value.StartsWith(Resources.qtProjectKeyword)
+ || x.Value.StartsWith(Resources.qtProjectV2Keyword))
+ .FirstOrDefault();
+ if (projKeyword == null)
+ return false;
+ var oldVersion = ParseProjectFormatVersion(projKeyword.Value);
+ if (oldVersion.HasValue && oldVersion.Value == Resources.qtProjectFormatVersion)
+ return true; // nothing to do!
+
+ 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;
+
+ var uncategorizedPropertyGroups = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Where(pg => pg.Attribute("Label") == null)
+ .ToList();
+
+ var propertyGroups = new Dictionary<string, XElement>();
+
+ // Upgrading from <= v3.2?
+ if (!oldVersion.HasValue
+ || oldVersion.Value < Resources.qtMinFormatVersion_PropertyEval) {
+
+ // Find import of default Qt properties
+ var qtDefaultProps = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ImportGroup")
+ .Elements(ns + "Import")
+ .Where(pg => Path.GetFileName((string)pg.Attribute("Project"))
+ .Equals("qt_defaults.props", StringComparison.InvariantCultureIgnoreCase))
+ .Select(pg => pg.Parent)
+ .FirstOrDefault();
+
+ // Create uncategorized property groups
+ foreach (var config in configs) {
+ string condition = string.Format("'$(Configuration)|$(Platform)'=='{0}'",
+ (string)config.Attribute("Include"));
+ var group = new XElement(ns + "PropertyGroup",
+ new XAttribute("Condition", condition));
+ propertyGroups[condition] = group;
+ // Insert uncategorized groups after Qt defaults, if found
+ if (qtDefaultProps != null)
+ qtDefaultProps.AddAfterSelf(group);
+ }
+
+ // Move uncategorized properties to newly created groups
+ foreach (var pg in uncategorizedPropertyGroups) {
+ foreach (var p in pg.Elements().ToList()) {
+ var condition = p.Attribute("Condition") ?? pg.Attribute("Condition");
+ XElement configPropertyGroup = null;
+ if (condition != null)
+ propertyGroups.TryGetValue((string)condition, out configPropertyGroup);
+ if (configPropertyGroup != null) {
+ p.Remove();
+ p.SetAttributeValue("Condition", null);
+ configPropertyGroup.Add(p);
+ }
+ }
+ if (!pg.Elements().Any())
+ pg.Remove();
+ }
+ }
+
+ // Upgrading from <= v3.1?
+ if (!oldVersion.HasValue
+ || oldVersion.Value < Resources.qtMinFormatVersion_GlobalQtMsBuildProperty) {
+
+ // Move Qt/MSBuild path to global property
+ var qtMsBuildProperty = globals
+ .ElementsAfterSelf(ns + "PropertyGroup")
+ .Elements(ns + "QtMsBuild")
+ .FirstOrDefault();
+ if (qtMsBuildProperty != null) {
+ var qtMsBuildPropertyGroup = qtMsBuildProperty.Parent;
+ qtMsBuildProperty.Remove();
+ qtMsBuildProperty.SetAttributeValue("Condition",
+ (string)qtMsBuildPropertyGroup.Attribute("Condition"));
+ globals.Add(qtMsBuildProperty);
+ qtMsBuildPropertyGroup.Remove();
+ }
+ }
+ if (oldVersion.HasValue
+ && oldVersion.Value > Resources.qtMinFormatVersion_Settings) {
+ this[Files.Project].isDirty = true;
+ Commit();
+ return true;
+ }
+
+ // Upgrading from v3.0?
+ Dictionary<string, XElement> oldQtInstall = null;
+ Dictionary<string, XElement> oldQtSettings = null;
+ if (oldVersion.HasValue && oldVersion.Value == Resources.qtMinFormatVersion_Settings) {
+ oldQtInstall = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Elements(ns + "QtInstall")
+ .ToDictionary(x => (string)x.Parent.Attribute("Condition"));
+ oldQtInstall.Values.ToList()
+ .ForEach(x => x.Remove());
+
+ oldQtSettings = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Where(x => (string)x.Attribute("Label") == "QtSettings")
+ .ToDictionary(x => (string)x.Attribute("Condition"));
+ oldQtSettings.Values.ToList()
+ .ForEach(x => x.Remove());
+ }
+
+ // Find location for import of qt.props and for the QtSettings property group:
+ // (cf. ".vcxproj file elements" https://docs.microsoft.com/en-us/cpp/build/reference/vcxproj-file-structure?view=vs-2019#vcxproj-file-elements)
+ XElement insertionPoint = null;
+
+ // * After the last UserMacros property group
+ insertionPoint = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Where(x => (string)x.Attribute("Label") == "UserMacros")
+ .LastOrDefault();
+
+ // * After the last PropertySheets import group
+ insertionPoint = (insertionPoint != null) ? insertionPoint : this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ImportGroup")
+ .Where(x => (string)x.Attribute("Label") == "PropertySheets")
+ .LastOrDefault();
+
+ // * Before the first ItemDefinitionGroup
+ insertionPoint = (insertionPoint != null) ? insertionPoint : this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Select(x => x.ElementsBeforeSelf().Last())
+ .FirstOrDefault();
+
+ // * Before the first ItemGroup
+ insertionPoint = (insertionPoint != null) ? insertionPoint : this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Select(x => x.ElementsBeforeSelf().Last())
+ .FirstOrDefault();
+
+ // * Before the import of Microsoft.Cpp.targets
+ insertionPoint = (insertionPoint != null) ? insertionPoint : this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "Import")
+ .Where(x =>
+ (string)x.Attribute("Project") == @"$(VCTargetsPath)\Microsoft.Cpp.targets")
+ .Select(x => x.ElementsBeforeSelf().Last())
+ .FirstOrDefault();
+
+ // * At the end of the file
+ insertionPoint = (insertionPoint != null) ? insertionPoint : this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements()
+ .LastOrDefault();
+
+ if (insertionPoint == null)
+ return false;
+
+ // Move import of qt.props to insertion point
+ if (qtPropsImport.Parent.Elements().SingleOrDefault() == qtPropsImport)
+ qtPropsImport.Parent.Remove(); // Remove import group
+ else
+ qtPropsImport.Remove(); // Remove import (group contains other imports)
+ insertionPoint.AddAfterSelf(
+ new XElement(ns + "ImportGroup",
+ new XAttribute("Condition", @"Exists('$(QtMsBuild)\qt.props')"),
+ new XElement(ns + "Import",
+ new XAttribute("Project", @"$(QtMsBuild)\qt.props"))));
+
+ // Create QtSettings property group above import of qt.props
+ var qtSettings = new List<XElement>();
+ foreach (var config in configs) {
+ var configQtSettings = new XElement(ns + "PropertyGroup",
+ new XAttribute("Label", "QtSettings"),
+ new XAttribute("Condition",
+ string.Format("'$(Configuration)|$(Platform)'=='{0}'",
+ (string)config.Attribute("Include"))));
+ insertionPoint.AddAfterSelf(configQtSettings);
+ qtSettings.Add(configQtSettings);
+ }
+
+ // Add uncategorized property groups
+ foreach (XElement propertyGroup in propertyGroups.Values)
+ insertionPoint.AddAfterSelf(propertyGroup);
+
+ // Add import of default property values
+ insertionPoint.AddAfterSelf(
+ new XElement(ns + "ImportGroup",
+ new XAttribute("Condition", @"Exists('$(QtMsBuild)\qt_defaults.props')"),
+ new XElement(ns + "Import",
+ new XAttribute("Project", @"$(QtMsBuild)\qt_defaults.props"))));
+
+ //// Upgrading from v3.0: move Qt settings to newly created import groups
+ if (oldVersion.HasValue && oldVersion.Value == Resources.qtMinFormatVersion_Settings) {
+ foreach (var configQtSettings in qtSettings) {
+ var configCondition = (string)configQtSettings.Attribute("Condition");
+
+ XElement oldConfigQtInstall;
+ if (oldQtInstall.TryGetValue(configCondition, out oldConfigQtInstall))
+ configQtSettings.Add(oldConfigQtInstall);
+
+ XElement oldConfigQtSettings;
+ if (oldQtSettings.TryGetValue(configCondition, out oldConfigQtSettings)) {
+ foreach (var qtSetting in oldConfigQtSettings.Elements())
+ configQtSettings.Add(qtSetting);
+ }
+ }
+
+ this[Files.Project].isDirty = true;
+ Commit();
+ return true;
+ }
+
+ //// Upgrading from v2.0
+
+ // 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();
+
+ // 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.projLabelQtSettings)
+ .ToList()
+ .ForEach(config =>
+ {
+ string qtInstallValue = defaultVersionName;
+ if (userProps != null) {
+ 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);
+ qtInstallValue = (string)userProps.Attribute(qtInstallName);
+ }
+ }
+ if (!string.IsNullOrEmpty(qtInstallValue))
+ config.Add(new XElement(ns + "QtInstall", qtInstallValue));
+ });
+
+ // Get C++ compiler properties
+ var compiler = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Elements(ns + "ClCompile");
+
+ // Get linker properties
+ var linker = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Elements(ns + "Link");
+
+ // 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()) {
+
+ if (IsModuleUsed(module, compiler, linker)) {
+
+ // 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(module.LibRelease);
+ moduleLibs.Add(module.LibDebug);
+
+ if (IsPrivateIncludePathUsed(module, compiler)) {
+ // Qt private module names, to copy to QtModules property
+ moduleNames.UnionWith(module.proVarQT.Split(' ')
+ .Select(x => string.Format("{0}-private", x)));
+ }
+ }
+ }
+
+ // Remove Qt module macros from compiler properties
+ foreach (var defines in compiler.Elements(ns + "PreprocessorDefinitions")) {
+ defines.SetValue(string.Join(";", defines.Value.Split(';')
+ .Where(x => !moduleDefines.Contains(x))));
+ }
+
+ // Remove Qt module include paths from compiler properties
+ foreach (var inclPath in compiler.Elements(ns + "AdditionalIncludeDirectories")) {
+ inclPath.SetValue(string.Join(";", inclPath.Value.Split(';')
+ .Select(x => Unquote(x))
+ // Exclude paths rooted on $(QTDIR)
+ .Where(x => !x.StartsWith("$(QTDIR)", IGNORE_CASE))));
+ }
+
+ // Remove Qt module libraries from linker properties
+ foreach (var libs in linker.Elements(ns + "AdditionalDependencies")) {
+ libs.SetValue(string.Join(";", libs.Value.Split(';')
+ .Where(x => !moduleLibs.Contains(Path.GetFileName(Unquote(x)), IGNORE_CASE_))));
+ }
+
+ // Remove Qt lib path from linker properties
+ foreach (var libs in linker.Elements(ns + "AdditionalLibraryDirectories")) {
+ libs.SetValue(string.Join(";", libs.Value.Split(';')
+ .Select(x => Unquote(x))
+ // Exclude paths rooted on $(QTDIR)
+ .Where(x => !x.StartsWith("$(QTDIR)", IGNORE_CASE))));
+ }
+
+ // 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))));
+
+ // Remove project user properties (old format)
+ if (userProps != null) {
+ userProps.Attributes().ToList().ForEach(userProp =>
+ {
+ if (userProp.Name.LocalName == "lupdateOptions"
+ || userProp.Name.LocalName == "lupdateOnBuild"
+ || userProp.Name.LocalName == "lreleaseOptions"
+ || userProp.Name.LocalName == "MocDir"
+ || userProp.Name.LocalName == "MocOptions"
+ || userProp.Name.LocalName == "RccDir"
+ || userProp.Name.LocalName == "UicDir"
+ || userProp.Name.LocalName.StartsWith("Qt5Version_x0020_"))
+ {
+ userProp.Remove();
+ }
+ });
+ }
+
+ // Remove old properties from .user file
+ if (this[Files.User].xml != null) {
+ this[Files.User].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Elements()
+ .ToList()
+ .ForEach(userProp =>
+ {
+ if (userProp.Name.LocalName == "QTDIR"
+ || userProp.Name.LocalName == "QmlDebug"
+ || userProp.Name.LocalName == "QmlDebugSettings"
+ || (userProp.Name.LocalName == "LocalDebuggerCommandArguments"
+ && (string)userProp == "$(QmlDebug)"
+ )
+ || (userProp.Name.LocalName == "LocalDebuggerEnvironment"
+ && (string)userProp == "PATH=$(QTDIR)\\bin%3b$(PATH)"
+ )
+ ) {
+ userProp.Remove();
+ }
+ });
+ this[Files.User].isDirty = true;
+ }
+
+ // Convert OutputFile --> <tool>Dir + <tool>FileName
+ var qtItems = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .SelectMany(x => x.Elements(ns + "ItemDefinitionGroup")
+ .Union(x.Elements(ns + "ItemGroup")))
+ .SelectMany(x => x.Elements(ns + "QtMoc")
+ .Union(x.Elements(ns + "QtRcc"))
+ .Union(x.Elements(ns + "QtUic")));
+ foreach (var qtItem in qtItems) {
+ var outputFile = qtItem.Element(ns + "OutputFile");
+ if (outputFile != null) {
+ var qtTool = qtItem.Name.LocalName;
+ var outDir = Path.GetDirectoryName(outputFile.Value);
+ var outFileName = Path.GetFileName(outputFile.Value);
+ if (!string.IsNullOrEmpty(outDir))
+ qtItem.Add(new XElement(ns + qtTool + "Dir", outDir));
+ else
+ qtItem.Add(new XElement(ns + qtTool + "Dir", "$(ProjectDir)"));
+ qtItem.Add(new XElement(ns + qtTool + "FileName", outFileName));
+ }
+ }
+
+ // Remove old properties from project items
+ var oldQtProps = new[] { "QTDIR", "InputFile", "OutputFile" };
+ var oldCppProps = new[] { "IncludePath", "Define", "Undefine" };
+ var oldPropsAny = oldQtProps.Union(oldCppProps);
+ this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Union(this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup"))
+ .Elements().ToList().ForEach(item =>
+ {
+ var itemName = item.Name.LocalName;
+ item.Elements().ToList().ForEach(itemProp =>
+ {
+ var propName = itemProp.Name.LocalName;
+ if ((itemName == "QtMoc" && oldPropsAny.Contains(propName))
+ || (itemName == "QtRcc" && oldQtProps.Contains(propName))
+ || (itemName == "QtUic" && oldQtProps.Contains(propName))
+ || (itemName == "QtRepc" && oldPropsAny.Contains(propName))
+ ) {
+ itemProp.Remove();
+ }
+ });
+ });
+
+ this[Files.Project].isDirty = true;
+ Commit();
+ return true;
+ }
+
+ bool IsModuleUsed(
+ QtModuleInfo module,
+ IEnumerable<XElement> compiler,
+ IEnumerable<XElement> linker)
+ {
+ // Module .lib is present in linker additional dependencies
+ if (linker.Elements(ns + "AdditionalDependencies")
+ .SelectMany(x => x.Value.Split(';'))
+ .Any(x => Path.GetFileName(Unquote(x)).Equals(module.LibRelease, IGNORE_CASE)
+ || Path.GetFileName(Unquote(x)).Equals(module.LibDebug, IGNORE_CASE))) {
+ return true;
+ }
+
+ // Module macro is present in pre-processor definitions
+ if (compiler.Elements(ns + "PreprocessorDefinitions")
+ .SelectMany(x => x.Value.Split(';'))
+ .Any(x => module.Defines.Contains(x))) {
+ return true;
+ }
+
+ // Module is not present
+ return false;
+ }
+
+ bool IsPrivateIncludePathUsed(
+ QtModuleInfo module,
+ IEnumerable<XElement> compiler)
+ {
+ // Module private header path is present in compiler include dirs
+ var privateIncludePattern = new Regex(string.Format(
+ @"^\$\(QTDIR\)[\\\/]include[\\\/]{0}[\\\/]\d+\.\d+\.\d+",
+ module.LibraryPrefix));
+ if (compiler.Elements(ns + "AdditionalIncludeDirectories")
+ .SelectMany(x => x.Value.Split(';'))
+ .Any(x => privateIncludePattern.IsMatch(x))) {
+ return true;
+ }
+
+ // Private header path is not present
+ return false;
+ }
+
+ public bool SetDefaultWindowsSDKVersion(string winSDKVersion)
+ {
+ var xGlobals = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "PropertyGroup")
+ .Where(x => (string)x.Attribute("Label") == "Globals")
+ .FirstOrDefault();
+ if (xGlobals == null)
+ return false;
+ if (xGlobals.Element(ns + "WindowsTargetPlatformVersion") != null)
+ return true;
+ xGlobals.Add(
+ new XElement(ns + "WindowsTargetPlatformVersion", winSDKVersion));
+
+ this[Files.Project].isDirty = true;
+ Commit();
+ return true;
+ }
+
+ public bool AddQtMsBuildReferences()
+ {
+ var isQtMsBuildEnabled = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ImportGroup")
+ .Elements(ns + "Import")
+ .Where(x =>
+ x.Attribute("Project").Value == @"$(QtMsBuild)\qt.props")
+ .Any();
+ if (isQtMsBuildEnabled)
+ return true;
+
+ var xImportCppProps = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "Import")
+ .Where(x =>
+ x.Attribute("Project").Value == @"$(VCTargetsPath)\Microsoft.Cpp.props")
+ .FirstOrDefault();
+ if (xImportCppProps == null)
+ return false;
+
+ var xImportCppTargets = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "Import")
+ .Where(x =>
+ x.Attribute("Project").Value == @"$(VCTargetsPath)\Microsoft.Cpp.targets")
+ .FirstOrDefault();
+ if (xImportCppTargets == null)
+ return false;
+
+ xImportCppProps.AddAfterSelf(
+ new XElement(ns + "PropertyGroup",
+ new XAttribute("Condition",
+ @"'$(QtMsBuild)'=='' " +
+ @"or !Exists('$(QtMsBuild)\qt.targets')"),
+ new XElement(ns + "QtMsBuild",
+ @"$(MSBuildProjectDirectory)\QtMsBuild")),
+
+ new XElement(ns + "Target",
+ new XAttribute("Name", "QtMsBuildNotFound"),
+ new XAttribute("BeforeTargets", "CustomBuild;ClCompile"),
+ new XAttribute("Condition",
+ @"!Exists('$(QtMsBuild)\qt.targets') " +
+ @"or !Exists('$(QtMsBuild)\qt.props')"),
+ new XElement(ns + "Message",
+ new XAttribute("Importance", "High"),
+ new XAttribute("Text",
+ "QtMsBuild: could not locate qt.targets, qt.props; " +
+ "project may not build correctly."))),
+
+ new XElement(ns + "ImportGroup",
+ new XAttribute("Condition", @"Exists('$(QtMsBuild)\qt.props')"),
+ new XElement(ns + "Import",
+ new XAttribute("Project", @"$(QtMsBuild)\qt.props"))));
+
+ xImportCppTargets.AddAfterSelf(
+ new XElement(ns + "ImportGroup",
+ new XAttribute("Condition", @"Exists('$(QtMsBuild)\qt.targets')"),
+ new XElement(ns + "Import",
+ new XAttribute("Project", @"$(QtMsBuild)\qt.targets"))));
+
+ this[Files.Project].isDirty = true;
+ Commit();
+ return true;
+ }
+
+ delegate string ItemCommandLineReplacement(string itemName, string cmdLine);
+
+ bool SetCommandLines(
+ QtMsBuildContainer qtMsBuild,
+ IEnumerable<XElement> configurations,
+ IEnumerable<XElement> customBuilds,
+ string toolExec,
+ string itemType,
+ string workingDir,
+ IEnumerable<ItemCommandLineReplacement> extraReplacements)
+ {
+ var query = from customBuild in customBuilds
+ let itemName = customBuild.Attribute("Include").Value
+ from config in configurations
+ from command in customBuild.Elements(ns + "Command")
+ where command.Attribute("Condition").Value
+ == string.Format(
+ "'$(Configuration)|$(Platform)'=='{0}'",
+ (string)config.Attribute("Include"))
+ select new { customBuild, itemName, config, command };
+
+ var projPath = this[Files.Project].filePath;
+ bool error = false;
+ using (var evaluator = new MSBuildEvaluator(this[Files.Project])) {
+ foreach (var row in query) {
+
+ var configId = (string)row.config.Attribute("Include");
+ if (!row.command.Value.Contains(toolExec)) {
+ Messages.PaneMessageSafe(VsServiceProvider.GetService<DTE>(), string.Format(
+ "{0}: warning: [{1}] converting \"{2}\", configuration \"{3}\": " +
+ "tool not found: \"{4}\"; applying default options",
+ projPath, itemType, row.itemName, configId, toolExec), 5000);
+ continue;
+ }
+
+ XElement item;
+ row.customBuild.Add(item =
+ new XElement(ns + itemType,
+ new XAttribute("Include", row.itemName),
+ new XAttribute("ConfigName", configId)));
+ var configName = (string)row.config.Element(ns + "Configuration");
+ var platformName = (string)row.config.Element(ns + "Platform");
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Replace fixed values with VS macros
+ //
+ // * Filename, e.g. foo.ui --> %(Filename)%(Extension)
+ var commandLine = row.command.Value
+ .Replace(Path.GetFileName(row.itemName), "%(Filename)%(Extension)",
+ StringComparison.InvariantCultureIgnoreCase);
+ //
+ // * Context specific, e.g. ui_foo.h --> ui_%(FileName).h
+ foreach (var replace in extraReplacements)
+ commandLine = replace(row.itemName, commandLine);
+ //
+ // * Configuration/platform, e.g. x64\Debug --> $(Platform)\$(Configuration)
+ commandLine = commandLine
+ .Replace(configName, "$(Configuration)",
+ StringComparison.InvariantCultureIgnoreCase)
+ .Replace(platformName, "$(Platform)",
+ StringComparison.InvariantCultureIgnoreCase);
+
+ evaluator.Properties.Clear();
+ foreach (var configProp in row.config.Elements())
+ evaluator.Properties.Add(configProp.Name.LocalName, (string)configProp);
+ if (!qtMsBuild.SetCommandLine(itemType, item, commandLine, evaluator)) {
+ int lineNumber = 1;
+ var errorLine = row.command as IXmlLineInfo;
+ if (errorLine != null && errorLine.HasLineInfo())
+ lineNumber = errorLine.LineNumber;
+
+ Messages.PaneMessageSafe(VsServiceProvider.GetService<DTE>(), string.Format(
+ "{0}({1}): error: [{2}] converting \"{3}\", configuration \"{4}\": " +
+ "failed to convert custom build command",
+ projPath, lineNumber, itemType, row.itemName, configId), 5000);
+
+ item.Remove();
+ error = true;
+ }
+ }
+ }
+
+ return !error;
+ }
+
+ IEnumerable<XElement> GetCustomBuilds(string toolExecName)
+ {
+ return this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "CustomBuild")
+ .Where(x => x.Elements(ns + "Command")
+ .Where(y => (y.Value.Contains(toolExecName))).Any());
+ }
+
+ void FinalizeProjectChanges(List<XElement> customBuilds, string itemTypeName)
+ {
+ customBuilds
+ .Elements().Where(
+ elem => elem.Name.LocalName != itemTypeName)
+ .ToList().ForEach(oldElem => oldElem.Remove());
+
+ customBuilds.Elements(ns + itemTypeName).ToList().ForEach(item =>
+ {
+ item.Elements().ToList().ForEach(prop =>
+ {
+ string configName = prop.Parent.Attribute("ConfigName").Value;
+ prop.SetAttributeValue("Condition",
+ string.Format("'$(Configuration)|$(Platform)'=='{0}'", configName));
+ prop.Remove();
+ item.Parent.Add(prop);
+ });
+ item.Remove();
+ });
+
+ customBuilds.ForEach(customBuild =>
+ {
+ var filterCustomBuild = this[Files.Filters]?.xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "CustomBuild")
+ .Where(filterItem =>
+ filterItem.Attribute("Include").Value
+ == customBuild.Attribute("Include").Value)
+ .FirstOrDefault();
+ if (filterCustomBuild != null) {
+ filterCustomBuild.Name = ns + itemTypeName;
+ this[Files.Filters].isDirty = true;
+ }
+
+ customBuild.Name = ns + itemTypeName;
+ this[Files.Project].isDirty = true;
+ });
+ }
+
+ string AddGeneratedFilesPath(string includePathList)
+ {
+ HashSet<string> includes = new HashSet<string> {
+ QtVSIPSettings.GetMocDirectory(),
+ QtVSIPSettings.GetRccDirectory(),
+ QtVSIPSettings.GetUicDirectory(),
+ };
+ foreach (var includePath in includePathList.Split(new char[] { ';' }))
+ includes.Add(includePath);
+ return string.Join<string>(";", includes);
+ }
+
+ string CustomBuildMocInput(XElement cbt)
+ {
+ var commandLine = (string)cbt.Element(ns + "Command");
+ Dictionary<QtMoc.Property, string> properties;
+ using (var evaluator = new MSBuildEvaluator(this[Files.Project])) {
+ if (!QtMsBuildContainer.QtMocInstance.ParseCommandLine(
+ commandLine, evaluator, out properties)) {
+ return (string)cbt.Attribute("Include");
+ }
+ }
+ string ouputFile;
+ if (!properties.TryGetValue(QtMoc.Property.InputFile, out ouputFile))
+ return (string)cbt.Attribute("Include");
+ return ouputFile;
+ }
+
+ bool RemoveGeneratedFiles(
+ string projDir,
+ List<CustomBuildEval> cbEvals,
+ string configName,
+ string itemName,
+ Dictionary<string, List<XElement>> projItemsByPath,
+ Dictionary<string, List<XElement>> filterItemsByPath)
+ {
+ //remove items with generated files
+ bool hasGeneratedFiles = false;
+ var cbEval = cbEvals
+ .Where(x => x.ProjectConfig == configName && x.Identity == itemName)
+ .FirstOrDefault();
+ if (cbEval != null) {
+ var outputFiles = cbEval.Outputs
+ .Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => HelperFunctions.CanonicalPath(
+ Path.IsPathRooted(x) ? x : Path.Combine(projDir, x)));
+ var outputItems = new List<XElement>();
+ foreach (var outputFile in outputFiles) {
+ List<XElement> mocOutput = null;
+ if (projItemsByPath.TryGetValue(outputFile, out mocOutput)) {
+ outputItems.AddRange(mocOutput);
+ hasGeneratedFiles |= hasGeneratedFiles ? true : mocOutput
+ .Where(x => !x.Elements(ns + "ExcludedFromBuild")
+ .Where(y =>
+ (string)y.Attribute("Condition") == string.Format(
+ "'$(Configuration)|$(Platform)'=='{0}'", configName)
+ && y.Value == "true")
+ .Any())
+ .Any();
+ }
+ if (filterItemsByPath.TryGetValue(outputFile, out mocOutput))
+ outputItems.AddRange(mocOutput);
+ }
+ foreach (var item in outputItems.Where(x => x.Parent != null))
+ item.Remove();
+ }
+ return hasGeneratedFiles;
+ }
+
+ public bool ConvertCustomBuildToQtMsBuild()
+ {
+ var cbEvals = EvaluateCustomBuild();
+
+ var qtMsBuild = new QtMsBuildContainer(new MsBuildConverterProvider());
+ qtMsBuild.BeginSetItemProperties();
+
+ var projDir = Path.GetDirectoryName(this[Files.Project].filePath);
+
+ var configurations = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "ProjectConfiguration");
+
+ var projItemsByPath = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements()
+ .Where(x => ((string)x.Attribute("Include"))
+ .IndexOfAny(Path.GetInvalidPathChars()) == -1)
+ .GroupBy(x => HelperFunctions.CanonicalPath(
+ Path.Combine(projDir, (string)x.Attribute("Include"))),
+ StringComparer.InvariantCultureIgnoreCase)
+ .ToDictionary(x => x.Key, x => new List<XElement>(x));
+
+ var filterItemsByPath = (this[Files.Filters].xml != null)
+ ? this[Files.Filters].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements()
+ .Where(x => ((string)x.Attribute("Include"))
+ .IndexOfAny(Path.GetInvalidPathChars()) == -1)
+ .GroupBy(x => HelperFunctions.CanonicalPath(
+ Path.Combine(projDir, (string)x.Attribute("Include"))),
+ StringComparer.InvariantCultureIgnoreCase)
+ .ToDictionary(x => x.Key, x => new List<XElement>(x))
+ : new Dictionary<string, List<XElement>>();
+
+ var cppIncludePaths = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemDefinitionGroup")
+ .Elements(ns + "ClCompile")
+ .Elements(ns + "AdditionalIncludeDirectories");
+
+ //add generated files path to C++ additional include dirs
+ foreach (var cppIncludePath in cppIncludePaths)
+ cppIncludePath.Value = AddGeneratedFilesPath((string)cppIncludePath);
+
+ // replace each set of .moc.cbt custom build steps
+ // with a single .cpp custom build step
+ var mocCbtCustomBuilds = GetCustomBuilds(QtMoc.ToolExecName)
+ .Where(x =>
+ ((string)x.Attribute("Include")).EndsWith(".cbt",
+ StringComparison.InvariantCultureIgnoreCase)
+ || ((string)x.Attribute("Include")).EndsWith(".moc",
+ StringComparison.InvariantCultureIgnoreCase))
+ .GroupBy(cbt => CustomBuildMocInput(cbt));
+
+ List<XElement> cbtToRemove = new List<XElement>();
+ foreach (var cbtGroup in mocCbtCustomBuilds) {
+
+ //create new CustomBuild item for .cpp
+ var newCbt = new XElement(ns + "CustomBuild",
+ new XAttribute("Include", cbtGroup.Key),
+ new XElement(ns + "FileType", "Document"));
+
+ //add properties from .moc.cbt items
+ List<string> cbtPropertyNames = new List<string> {
+ "AdditionalInputs",
+ "Command",
+ "Message",
+ "Outputs",
+ };
+ foreach (var cbt in cbtGroup) {
+ var enabledProperties = cbt.Elements().Where(x =>
+ cbtPropertyNames.Contains(x.Name.LocalName)
+ && !x.Parent.Elements(ns + "ExcludedFromBuild").Where(y =>
+ (string)x.Attribute("Condition") == (string)y.Attribute("Condition"))
+ .Any());
+ foreach (var property in enabledProperties)
+ newCbt.Add(new XElement(property));
+ cbtToRemove.Add(cbt);
+ }
+ cbtGroup.First().AddBeforeSelf(newCbt);
+
+ //remove ClCompile item (cannot have duplicate items)
+ var cppMocItems = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "ClCompile")
+ .Where(x =>
+ cbtGroup.Key.Equals((string)x.Attribute("Include"),
+ StringComparison.InvariantCultureIgnoreCase));
+ foreach (var cppMocItem in cppMocItems)
+ cppMocItem.Remove();
+
+ //change type of item in filter
+ cppMocItems = this[Files.Filters]?.xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "ClCompile")
+ .Where(x =>
+ cbtGroup.Key.Equals((string)x.Attribute("Include"),
+ StringComparison.InvariantCultureIgnoreCase));
+ foreach (var cppMocItem in cppMocItems)
+ cppMocItem.Name = ns + "CustomBuild";
+ }
+
+ //remove .moc.cbt CustomBuild items
+ cbtToRemove.ForEach(x => x.Remove());
+
+ //convert moc custom build steps
+ var mocCustomBuilds = GetCustomBuilds(QtMoc.ToolExecName);
+ if (!SetCommandLines(qtMsBuild, configurations, mocCustomBuilds,
+ QtMoc.ToolExecName, QtMoc.ItemTypeName,
+ Path.GetDirectoryName(this[Files.Project].filePath),
+ new ItemCommandLineReplacement[]
+ {
+ (item, cmdLine) => cmdLine.Replace(
+ string.Format(@"\moc_{0}.cpp", Path.GetFileNameWithoutExtension(item)),
+ @"\moc_%(Filename).cpp", StringComparison.InvariantCultureIgnoreCase)
+ .Replace(
+ string.Format(" -o moc_{0}.cpp", Path.GetFileNameWithoutExtension(item)),
+ @" -o $(ProjectDir)\moc_%(Filename).cpp",
+ StringComparison.InvariantCultureIgnoreCase),
+
+ (item, cmdLine) => cmdLine.Replace(
+ string.Format(@"\{0}.moc", Path.GetFileNameWithoutExtension(item)),
+ @"\%(Filename).moc", StringComparison.InvariantCultureIgnoreCase)
+ .Replace(
+ string.Format(" -o {0}.moc", Path.GetFileNameWithoutExtension(item)),
+ @" -o $(ProjectDir)\%(Filename).moc",
+ StringComparison.InvariantCultureIgnoreCase),
+ })) {
+ Rollback();
+ return false;
+ }
+ List<XElement> mocDisableDynamicSource = new List<XElement>();
+ foreach (var qtMoc in mocCustomBuilds.Elements(ns + QtMoc.ItemTypeName)) {
+ var itemName = (string)qtMoc.Attribute("Include");
+ var configName = (string)qtMoc.Attribute("ConfigName");
+
+ //remove items with generated files
+ var hasGeneratedFiles = RemoveGeneratedFiles(
+ projDir, cbEvals, configName, itemName,
+ projItemsByPath, filterItemsByPath);
+
+ //set properties
+ qtMsBuild.SetItemProperty(qtMoc,
+ QtMoc.Property.ExecutionDescription, "Moc'ing %(Identity)...");
+ qtMsBuild.SetItemProperty(qtMoc,
+ QtMoc.Property.InputFile, "%(FullPath)");
+ if (!HelperFunctions.IsSourceFile(itemName)) {
+ qtMsBuild.SetItemProperty(qtMoc,
+ QtMoc.Property.DynamicSource, "output");
+ if (!hasGeneratedFiles)
+ mocDisableDynamicSource.Add(qtMoc);
+ } else {
+ qtMsBuild.SetItemProperty(qtMoc,
+ QtMoc.Property.DynamicSource, "input");
+ }
+ var includePath = qtMsBuild.GetPropertyChangedValue(
+ QtMoc.Property.IncludePath, itemName, configName);
+ if (!string.IsNullOrEmpty(includePath)) {
+ qtMsBuild.SetItemProperty(qtMoc,
+ QtMoc.Property.IncludePath, AddGeneratedFilesPath(includePath));
+ }
+ }
+
+ //convert rcc custom build steps
+ var rccCustomBuilds = GetCustomBuilds(QtRcc.ToolExecName);
+ if (!SetCommandLines(qtMsBuild, configurations, rccCustomBuilds,
+ QtRcc.ToolExecName, QtRcc.ItemTypeName,
+ Path.GetDirectoryName(this[Files.Project].filePath),
+ new ItemCommandLineReplacement[]
+ {
+ (item, cmdLine) => cmdLine.Replace(
+ string.Format(@"\qrc_{0}.cpp", Path.GetFileNameWithoutExtension(item)),
+ @"\qrc_%(Filename).cpp", StringComparison.InvariantCultureIgnoreCase)
+ .Replace(
+ string.Format(" -o qrc_{0}.cpp", Path.GetFileNameWithoutExtension(item)),
+ @" -o $(ProjectDir)\qrc_%(Filename).cpp",
+ StringComparison.InvariantCultureIgnoreCase),
+ })) {
+ Rollback();
+ return false;
+ }
+ foreach (var qtRcc in rccCustomBuilds.Elements(ns + QtRcc.ItemTypeName)) {
+ var itemName = (string)qtRcc.Attribute("Include");
+ var configName = (string)qtRcc.Attribute("ConfigName");
+
+ //remove items with generated files
+ RemoveGeneratedFiles(projDir, cbEvals, configName, itemName,
+ projItemsByPath, filterItemsByPath);
+
+ //set properties
+ qtMsBuild.SetItemProperty(qtRcc,
+ QtRcc.Property.ExecutionDescription, "Rcc'ing %(Identity)...");
+ qtMsBuild.SetItemProperty(qtRcc,
+ QtRcc.Property.InputFile, "%(FullPath)");
+ }
+
+ //convert repc custom build steps
+ var repcCustomBuilds = GetCustomBuilds(QtRepc.ToolExecName);
+ if (!SetCommandLines(qtMsBuild, configurations, repcCustomBuilds,
+ QtRepc.ToolExecName, QtRepc.ItemTypeName,
+ Path.GetDirectoryName(this[Files.Project].filePath),
+ new ItemCommandLineReplacement[] { })) {
+ Rollback();
+ return false;
+ }
+ foreach (var qtRepc in repcCustomBuilds.Elements(ns + QtRepc.ItemTypeName)) {
+ var itemName = (string)qtRepc.Attribute("Include");
+ var configName = (string)qtRepc.Attribute("ConfigName");
+
+ //remove items with generated files
+ RemoveGeneratedFiles(projDir, cbEvals, configName, itemName,
+ projItemsByPath, filterItemsByPath);
+
+ //set properties
+ qtMsBuild.SetItemProperty(qtRepc,
+ QtRepc.Property.ExecutionDescription, "Repc'ing %(Identity)...");
+ qtMsBuild.SetItemProperty(qtRepc,
+ QtRepc.Property.InputFile, "%(FullPath)");
+ }
+
+
+ //convert uic custom build steps
+ var uicCustomBuilds = GetCustomBuilds(QtUic.ToolExecName);
+ if (!SetCommandLines(qtMsBuild, configurations, uicCustomBuilds,
+ QtUic.ToolExecName, QtUic.ItemTypeName,
+ Path.GetDirectoryName(this[Files.Project].filePath),
+ new ItemCommandLineReplacement[]
+ {
+ (item, cmdLine) => cmdLine.Replace(
+ string.Format(@"\ui_{0}.h", Path.GetFileNameWithoutExtension(item)),
+ @"\ui_%(Filename).h", StringComparison.InvariantCultureIgnoreCase)
+ .Replace(
+ string.Format(" -o ui_{0}.h", Path.GetFileNameWithoutExtension(item)),
+ @" -o $(ProjectDir)\ui_%(Filename).h",
+ StringComparison.InvariantCultureIgnoreCase),
+ })) {
+ Rollback();
+ return false;
+ }
+ foreach (var qtUic in uicCustomBuilds.Elements(ns + QtUic.ItemTypeName)) {
+ var itemName = (string)qtUic.Attribute("Include");
+ var configName = (string)qtUic.Attribute("ConfigName");
+
+ //remove items with generated files
+ RemoveGeneratedFiles(projDir, cbEvals, configName, itemName,
+ projItemsByPath, filterItemsByPath);
+
+ //set properties
+ qtMsBuild.SetItemProperty(qtUic,
+ QtUic.Property.ExecutionDescription, "Uic'ing %(Identity)...");
+ qtMsBuild.SetItemProperty(qtUic,
+ QtUic.Property.InputFile, "%(FullPath)");
+ }
+
+ qtMsBuild.EndSetItemProperties();
+
+ //disable dynamic C++ source for moc headers without generated files
+ //(needed for the case of #include "moc_foo.cpp" in source file)
+ foreach (var qtMoc in mocDisableDynamicSource) {
+ qtMsBuild.SetItemProperty(qtMoc,
+ QtMoc.Property.DynamicSource, "false");
+ }
+
+ FinalizeProjectChanges(mocCustomBuilds.ToList(), QtMoc.ItemTypeName);
+ FinalizeProjectChanges(rccCustomBuilds.ToList(), QtRcc.ItemTypeName);
+ FinalizeProjectChanges(repcCustomBuilds.ToList(), QtRepc.ItemTypeName);
+ FinalizeProjectChanges(uicCustomBuilds.ToList(), QtUic.ItemTypeName);
+
+ this[Files.Project].isDirty = this[Files.Filters].isDirty = true;
+ Commit();
+ return true;
+ }
+
+ bool TryReplaceTextInPlace(ref string text, Regex findWhat, string newText)
+ {
+ var match = findWhat.Match(text);
+ if (!match.Success)
+ return false;
+ do {
+ text = text.Remove(match.Index, match.Length).Insert(match.Index, newText);
+ match = findWhat.Match(text, match.Index);
+ } while (match.Success);
+
+ return true;
+ }
+
+ void ReplaceText(XElement xElem, Regex findWhat, string newText)
+ {
+ var elemValue = (string)xElem;
+ if (!string.IsNullOrEmpty(elemValue)
+ && TryReplaceTextInPlace(ref elemValue, findWhat, newText)) {
+ xElem.Value = elemValue;
+ }
+ }
+
+ void ReplaceText(XAttribute xAttr, Regex findWhat, string newText)
+ {
+ var attrValue = (string)xAttr;
+ if (!string.IsNullOrEmpty(attrValue)
+ && TryReplaceTextInPlace(ref attrValue, findWhat, newText)) {
+ xAttr.Value = attrValue;
+ }
+ }
+
+ /// <summary>
+ /// All path separators
+ /// </summary>
+ static readonly char[] slashChars = new[]
+ {
+ Path.DirectorySeparatorChar,
+ Path.AltDirectorySeparatorChar
+ };
+
+ /// <summary>
+ /// Pattern that matches one path separator char
+ /// </summary>
+ static readonly RegExpr slash = CharSet[slashChars];
+
+ /// <summary>
+ /// Gets a RegExpr that matches a given path, regardless
+ /// of case and varying directory separators
+ /// </summary>
+ static RegExpr GetPathPattern(string findWhatPath)
+ {
+ return
+ // Make pattern case-insensitive
+ IgnoreCase &
+ // Split path string by directory separators
+ findWhatPath.Split(slashChars, StringSplitOptions.RemoveEmptyEntries)
+ // Convert path parts to RegExpr (escapes regex special chars)
+ .Select(dirName => (RegExpr)dirName)
+ // Join all parts, separated by path separator pattern
+ .Aggregate((path, dirName) => path & slash & dirName);
+ }
+
+ public void ReplacePath(string oldPath, string newPath)
+ {
+ Uri srcUri = new Uri(Path.GetFullPath(oldPath));
+ Uri projUri = new Uri(this[Files.Project].filePath);
+
+ RegExpr absolutePath = GetPathPattern(srcUri.AbsolutePath);
+ RegExpr relativePath = GetPathPattern(projUri.MakeRelativeUri(srcUri).OriginalString);
+
+ Regex findWhat = (absolutePath | relativePath).Render().Regex;
+
+ foreach (var xElem in this[Files.Project].xml.Descendants()) {
+ if (!xElem.HasElements)
+ ReplaceText(xElem, findWhat, newPath);
+ foreach (var xAttr in xElem.Attributes())
+ ReplaceText(xAttr, findWhat, newPath);
+ }
+ this[Files.Project].isDirty = true;
+ Commit();
+ }
+
+ class MSBuildEvaluator : IVSMacroExpander, IDisposable
+ {
+ MsBuildXmlFile projFile;
+ string tempProjFilePath;
+ XElement evaluateTarget;
+ XElement evaluateProperty;
+ ProjectRootElement projRoot;
+ public Dictionary<string, string> expansionCache;
+
+ public Dictionary<string, string> Properties
+ {
+ get;
+ private set;
+ }
+
+ public MSBuildEvaluator(MsBuildXmlFile projFile)
+ {
+ this.projFile = projFile;
+ tempProjFilePath = string.Empty;
+ evaluateTarget = evaluateProperty = null;
+ expansionCache = new Dictionary<string, string>();
+ Properties = new Dictionary<string, string>();
+ }
+
+ public void Dispose()
+ {
+ if (evaluateTarget != null) {
+ evaluateTarget.Remove();
+ if (File.Exists(tempProjFilePath))
+ File.Delete(tempProjFilePath);
+ }
+ }
+
+ string ExpansionCacheKey(string stringToExpand)
+ {
+ var key = new StringBuilder();
+ foreach (var property in Properties)
+ key.AppendFormat("{0};{1};", property.Key, property.Value);
+ key.Append(stringToExpand);
+ return key.ToString();
+ }
+
+ bool TryExpansionCache(string stringToExpand, out string expandedString)
+ {
+ return expansionCache.TryGetValue(
+ ExpansionCacheKey(stringToExpand), out expandedString);
+ }
+
+ void AddToExpansionCache(string stringToExpand, string expandedString)
+ {
+ expansionCache[ExpansionCacheKey(stringToExpand)] = expandedString;
+ }
+
+ public string ExpandString(string stringToExpand)
+ {
+ string expandedString;
+ if (TryExpansionCache(stringToExpand, out expandedString))
+ return expandedString;
+
+ if (evaluateTarget == null) {
+ projFile.xmlCommitted.Root.Add(evaluateTarget = new XElement(ns + "Target",
+ new XAttribute("Name", "MSBuildEvaluatorTarget"),
+ new XElement(ns + "PropertyGroup",
+ evaluateProperty = new XElement(ns + "MSBuildEvaluatorProperty"))));
+ }
+ if (stringToExpand != (string)evaluateProperty) {
+ evaluateProperty.SetValue(stringToExpand);
+ if (!string.IsNullOrEmpty(tempProjFilePath) && File.Exists(tempProjFilePath))
+ File.Delete(tempProjFilePath);
+ tempProjFilePath = Path.Combine(
+ Path.GetDirectoryName(projFile.filePath),
+ Path.GetRandomFileName());
+ if (File.Exists(tempProjFilePath))
+ File.Delete(tempProjFilePath);
+ projFile.xmlCommitted.Save(tempProjFilePath);
+ projRoot = ProjectRootElement.Open(tempProjFilePath);
+ }
+ var projInst = new ProjectInstance(projRoot, Properties,
+ null, new ProjectCollection());
+ var buildRequest = new BuildRequestData(
+ projInst, new string[] { "MSBuildEvaluatorTarget" },
+ null, BuildRequestDataFlags.ProvideProjectStateAfterBuild);
+ var buildResult = BuildManager.DefaultBuildManager.Build(
+ new BuildParameters(), buildRequest);
+ expandedString = buildResult.ProjectStateAfterBuild
+ .GetPropertyValue("MSBuildEvaluatorProperty");
+
+ AddToExpansionCache(stringToExpand, expandedString);
+ return expandedString;
+ }
+ }
+
+ class CustomBuildEval
+ {
+ public string ProjectConfig { get; set; }
+ public string Identity { get; set; }
+ public string AdditionalInputs { get; set; }
+ public string Outputs { get; set; }
+ public string Message { get; set; }
+ public string Command { get; set; }
+ }
+
+ List<CustomBuildEval> EvaluateCustomBuild()
+ {
+ var eval = new List<CustomBuildEval>();
+
+ var pattern = new Regex(@"{([^}]+)}{([^}]+)}{([^}]+)}{([^}]+)}{([^}]+)}");
+
+ var projConfigs = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "ProjectConfiguration");
+
+ using (var evaluator = new MSBuildEvaluator(this[Files.Project])) {
+
+ foreach (var projConfig in projConfigs) {
+
+ evaluator.Properties.Clear();
+ foreach (var configProp in projConfig.Elements())
+ evaluator.Properties.Add(configProp.Name.LocalName, (string)configProp);
+
+ var expandedValue = evaluator.ExpandString(
+ "@(CustomBuild->'" +
+ "{%(Identity)}" +
+ "{%(AdditionalInputs)}" +
+ "{%(Outputs)}" +
+ "{%(Message)}" +
+ "{%(Command)}')");
+
+ foreach (Match cbEval in pattern.Matches(expandedValue)) {
+ eval.Add(new CustomBuildEval
+ {
+ ProjectConfig = (string)projConfig.Attribute("Include"),
+ Identity = cbEval.Groups[1].Value,
+ AdditionalInputs = cbEval.Groups[2].Value,
+ Outputs = cbEval.Groups[3].Value,
+ Message = cbEval.Groups[4].Value,
+ Command = cbEval.Groups[5].Value,
+ });
+ }
+ }
+ }
+
+ return eval;
+ }
+
+ public bool BuildTarget(string target)
+ {
+ if (this[Files.Project].isDirty)
+ return false;
+
+ var configurations = this[Files.Project].xml
+ .Elements(ns + "Project")
+ .Elements(ns + "ItemGroup")
+ .Elements(ns + "ProjectConfiguration");
+
+ using (var buildManager = new BuildManager()) {
+
+ foreach (var config in configurations) {
+
+ var configProps = config.Elements()
+ .ToDictionary(x => x.Name.LocalName, x => x.Value);
+
+ var projectInstance = new ProjectInstance(this[Files.Project].filePath,
+ new Dictionary<string, string>(configProps)
+ { { "QtVSToolsBuild", "true" } },
+ null, new ProjectCollection());
+
+ var buildRequest = new BuildRequestData(projectInstance,
+ targetsToBuild: new[] { target },
+ hostServices: null,
+ flags: BuildRequestDataFlags.ProvideProjectStateAfterBuild);
+
+ var result = buildManager.Build(new BuildParameters(), buildRequest);
+ if (result.OverallResult != BuildResultCode.Success)
+ return false;
+
+ }
+ }
+ return true;
+ }
+
+ static Regex ConditionParser =
+ new Regex(@"\'\$\(Configuration[^\)]*\)\|\$\(Platform[^\)]*\)\'\=\=\'([^\']+)\'");
+
+ class MsBuildConverterProvider : IPropertyStorageProvider
+ {
+ public string GetProperty(object propertyStorage, string itemType, string propertyName)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return "";
+ XElement item;
+ if (xmlPropertyStorage.Name.LocalName == "ItemDefinitionGroup") {
+ item = xmlPropertyStorage.Element(ns + itemType);
+ if (item == null)
+ return "";
+ } else {
+ item = xmlPropertyStorage;
+ }
+ var prop = item.Element(ns + propertyName);
+ if (prop == null)
+ return "";
+ return prop.Value;
+ }
+
+ public bool SetProperty(
+ object propertyStorage,
+ string itemType,
+ string propertyName,
+ string propertyValue)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return false;
+ XElement item;
+ if (xmlPropertyStorage.Name.LocalName == "ItemDefinitionGroup") {
+ item = xmlPropertyStorage.Element(ns + itemType);
+ if (item == null)
+ xmlPropertyStorage.Add(item = new XElement(ns + itemType));
+ } else {
+ item = xmlPropertyStorage;
+ }
+ var prop = item.Element(ns + propertyName);
+ if (prop != null)
+ prop.Value = propertyValue;
+ else
+ item.Add(new XElement(ns + propertyName, propertyValue));
+ return true;
+ }
+
+ public bool DeleteProperty(
+ object propertyStorage,
+ string itemType,
+ string propertyName)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return false;
+ XElement item;
+ if (xmlPropertyStorage.Name.LocalName == "ItemDefinitionGroup") {
+ item = xmlPropertyStorage.Element(ns + itemType);
+ if (item == null)
+ return true;
+ } else {
+ item = xmlPropertyStorage;
+ }
+
+ var prop = item.Element(ns + propertyName);
+ if (prop != null)
+ prop.Remove();
+ return true;
+ }
+
+ public string GetConfigName(object propertyStorage)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return "";
+ if (xmlPropertyStorage.Name.LocalName == "ItemDefinitionGroup") {
+ var configName = ConditionParser
+ .Match(xmlPropertyStorage.Attribute("Condition").Value);
+ if (!configName.Success || configName.Groups.Count <= 1)
+ return "";
+ return configName.Groups[1].Value;
+ }
+ return xmlPropertyStorage.Attribute("ConfigName").Value;
+ }
+
+ public string GetItemType(object propertyStorage)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return "";
+ if (xmlPropertyStorage.Name.LocalName == "ItemDefinitionGroup")
+ return "";
+ return xmlPropertyStorage.Name.LocalName;
+ }
+
+ public string GetItemName(object propertyStorage)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return "";
+ if (xmlPropertyStorage.Name.LocalName == "ItemDefinitionGroup")
+ return "";
+ return xmlPropertyStorage.Attribute("Include").Value;
+ }
+
+ public object GetParentProject(object propertyStorage)
+ {
+ XElement xmlPropertyStorage = propertyStorage as XElement;
+ if (xmlPropertyStorage == null)
+ return "";
+ if (xmlPropertyStorage.Document == null)
+ return null;
+ return xmlPropertyStorage.Document.Root;
+ }
+
+ public object GetProjectConfiguration(object project, string configName)
+ {
+ XElement xmlProject = project as XElement;
+ if (xmlProject == null)
+ return null;
+ return xmlProject.Elements(ns + "ItemDefinitionGroup")
+ .Where(config => config.Attribute("Condition").Value.Contains(configName))
+ .FirstOrDefault();
+ }
+
+ public IEnumerable<object> GetItems(
+ object project,
+ string itemType,
+ string configName = "")
+ {
+ XElement xmlProject = project as XElement;
+ if (xmlProject == null)
+ return new List<object>();
+ return
+ xmlProject.Elements(ns + "ItemGroup")
+ .Elements(ns + "CustomBuild")
+ .Elements(ns + itemType)
+ .Where(item => (
+ configName == ""
+ || item.Attribute("ConfigName").Value == configName))
+ .GroupBy(item => item.Attribute("Include").Value)
+ .Select(item => item.First());
+ }
+ }
+
+ }
+}