/****************************************************************************  
 | 
**  
 | 
** 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.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 --> <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(  
 | 
            QtModule 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(  
 | 
            QtModule 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.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<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());  
 | 
            }  
 | 
        }  
 | 
  
 | 
    }  
 | 
}  
 |