/**************************************************************************** ** ** 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 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; } /// /// Parser for project configuration conditional expressions of the type: /// /// '$(Configuration)|$(Platform)'=='_TOKEN_|_TOKEN_' /// /// 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; } } /// /// Parser for project format version string: /// /// QtVS_vNNN /// /// Parser _ProjectFormatVersion; Parser ProjectFormatVersion { get { if (_ProjectFormatVersion == null) { var expr = "QtVS_v" & new Token("VERSION", Char['0', '9'].Repeat(3)) { new Rule { 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("VERSION") .First(); } catch { return null; } } const StringComparison IGNORE_CASE = StringComparison.InvariantCultureIgnoreCase; readonly StringComparer IGNORE_CASE_ = StringComparer.InvariantCultureIgnoreCase; /// /// 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; /// /// true if successful 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(); // 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 oldQtInstall = null; Dictionary 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(); 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("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(); // Qt module macros, to remove from compiler macros property var moduleDefines = new HashSet(); // Qt module includes, to remove from compiler include directories property var moduleIncludePaths = new HashSet(); // Qt module link libraries, to remove from liker dependencies property var moduleLibs = new HashSet(); // Go through all known Qt modules and check which ones are currently being used foreach (var module in QtModules.Instance.GetAvailableModules()) { 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 --> Dir + 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( QtModule module, IEnumerable compiler, IEnumerable 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( QtModule module, IEnumerable 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 configurations, IEnumerable customBuilds, string toolExec, string itemType, string workingDir, IEnumerable 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.Print(string.Format( "{0}: warning: [{1}] converting \"{2}\", configuration \"{3}\": " + "tool not found: \"{4}\"; applying default options", projPath, itemType, row.itemName, configId, toolExec)); 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.Print(string.Format( "{0}({1}): error: [{2}] converting \"{3}\", configuration \"{4}\": " + "failed to convert custom build command", projPath, lineNumber, itemType, row.itemName, configId)); item.Remove(); error = true; } } } return !error; } IEnumerable 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 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 includes = new HashSet { QtVSIPSettings.GetMocDirectory(), QtVSIPSettings.GetRccDirectory(), QtVSIPSettings.GetUicDirectory(), }; foreach (var includePath in includePathList.Split(new char[] { ';' })) includes.Add(includePath); return string.Join(";", includes); } string CustomBuildMocInput(XElement cbt) { var commandLine = (string)cbt.Element(ns + "Command"); Dictionary 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 cbEvals, string configName, string itemName, Dictionary> projItemsByPath, Dictionary> 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(); foreach (var outputFile in outputFiles) { List 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(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(x)) : new Dictionary>(); 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 cbtToRemove = new List(); 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 cbtPropertyNames = new List { "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 mocDisableDynamicSource = new List(); 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; } } /// /// All path separators /// static readonly char[] slashChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; /// /// Pattern that matches one path separator char /// static readonly RegExpr slash = CharSet[slashChars]; /// /// Gets a RegExpr that matches a given path, regardless /// of case and varying directory separators /// 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 expansionCache; public Dictionary Properties { get; private set; } public MSBuildEvaluator(MsBuildXmlFile projFile) { this.projFile = projFile; tempProjFilePath = string.Empty; evaluateTarget = evaluateProperty = null; expansionCache = new Dictionary(); Properties = new Dictionary(); } 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 EvaluateCustomBuild() { var eval = new List(); 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(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 GetItems( object project, string itemType, string configName = "") { XElement xmlProject = project as XElement; if (xmlProject == null) return new List(); 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()); } } } }