Наша сборка Qt VS Tools
giy
2022-09-02 ca47896204482bf4a6979e3838bf7f09f61cebeb
QtVsTools.Core/HelperFunctions.cs
@@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
@@ -26,10 +26,6 @@
**
****************************************************************************/
using EnvDTE;
using Microsoft.VisualStudio.VCProjectEngine;
using Microsoft.Win32;
using QtVsTools.Core.QtMsBuild;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -40,105 +36,31 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.VCProjectEngine;
#if VS2017
using Microsoft.Win32;
#endif
using EnvDTE;
using Process = System.Diagnostics.Process;
namespace QtVsTools.Core
{
    using QtMsBuild;
    public static class HelperFunctions
    {
        public static string FindQtDirWithTools(Project project)
        {
            var versionManager = QtVersionManager.The();
            string projectQtVersion = null;
            if (IsQtProject(project))
                projectQtVersion = versionManager.GetProjectQtVersion(project);
            return FindQtDirWithTools(projectQtVersion);
        }
        public static string FindQtDirWithTools(string projectQtVersion)
        {
            string tool = null;
            return FindQtDirWithTools(tool, projectQtVersion);
        }
        public static string FindQtDirWithTools(string tool, string projectQtVersion)
        {
            if (!string.IsNullOrEmpty(tool)) {
                if (!tool.StartsWith("\\bin\\", StringComparison.OrdinalIgnoreCase))
                    tool = "\\bin\\" + tool;
                if (!tool.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                    tool += ".exe";
            }
            var versionManager = QtVersionManager.The();
            string qtDir = null;
            if (projectQtVersion != null)
                qtDir = versionManager.GetInstallPath(projectQtVersion);
            if (qtDir == null)
                qtDir = Environment.GetEnvironmentVariable("QTDIR");
            var found = false;
            if (tool == null) {
                found = File.Exists(qtDir + "\\bin\\designer.exe")
                    && File.Exists(qtDir + "\\bin\\linguist.exe");
            } else {
                found = File.Exists(qtDir + tool);
            }
            if (!found) {
                VersionInformation exactlyMatchingVersion = null;
                VersionInformation matchingVersion = null;
                VersionInformation somehowMatchingVersion = null;
                var viProjectQtVersion = versionManager.GetVersionInfo(projectQtVersion);
                foreach (var qtVersion in versionManager.GetVersions()) {
                    var vi = versionManager.GetVersionInfo(qtVersion);
                    if (tool == null) {
                        found = File.Exists(vi.qtDir + "\\bin\\designer.exe")
                            && File.Exists(vi.qtDir + "\\bin\\linguist.exe");
                    } else {
                        found = File.Exists(vi.qtDir + tool);
                    }
                    if (!found)
                        continue;
                    if (viProjectQtVersion != null
                        && vi.qtMajor == viProjectQtVersion.qtMajor
                        && vi.qtMinor == viProjectQtVersion.qtMinor) {
                        exactlyMatchingVersion = vi;
                        break;
                    }
                    if (matchingVersion == null
                        && viProjectQtVersion != null
                        && vi.qtMajor == viProjectQtVersion.qtMajor) {
                        matchingVersion = vi;
                    }
                    if (somehowMatchingVersion == null)
                        somehowMatchingVersion = vi;
                }
                if (exactlyMatchingVersion != null)
                    qtDir = exactlyMatchingVersion.qtDir;
                else if (matchingVersion != null)
                    qtDir = matchingVersion.qtDir;
                else if (somehowMatchingVersion != null)
                    qtDir = somehowMatchingVersion.qtDir;
                else
                    qtDir = null;
            }
            return qtDir;
        }
        static readonly HashSet<string> _sources = new HashSet<string>(new[] { ".c", ".cpp", ".cxx" },
            StringComparer.OrdinalIgnoreCase);
        static public bool IsSourceFile(string fileName)
        public static bool IsSourceFile(string fileName)
        {
            return _sources.Contains(Path.GetExtension(fileName));
        }
        static readonly HashSet<string> _headers = new HashSet<string>(new[] { ".h", ".hpp", ".hxx" },
            StringComparer.OrdinalIgnoreCase);
        static public bool IsHeaderFile(string fileName)
        public static bool IsHeaderFile(string fileName)
        {
            return _headers.Contains(Path.GetExtension(fileName));
        }
@@ -173,23 +95,28 @@
            return ".qml".Equals(Path.GetExtension(fileName), StringComparison.OrdinalIgnoreCase);
        }
        static public void SetDebuggingEnvironment(Project prj)
        public static void SetDebuggingEnvironment(Project prj)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            SetDebuggingEnvironment(prj, string.Empty);
        }
        static public void SetDebuggingEnvironment(Project prj, string solutionConfig)
        public static void SetDebuggingEnvironment(Project prj, string solutionConfig)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            SetDebuggingEnvironment(prj, "PATH=$(QTDIR)\\bin;$(PATH)", false, solutionConfig);
        }
        static public void SetDebuggingEnvironment(Project prj, string envpath, bool overwrite)
        public static void SetDebuggingEnvironment(Project prj, string envpath, bool overwrite)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            SetDebuggingEnvironment(prj, envpath, overwrite, string.Empty);
        }
        static public void SetDebuggingEnvironment(Project prj, string envpath, bool overwrite, string solutionConfig)
        public static void SetDebuggingEnvironment(Project prj, string envpath, bool overwrite, string solutionConfig)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (QtProject.GetFormatVersion(prj) >= Resources.qtMinFormatVersion_Settings)
                return;
@@ -252,28 +179,29 @@
            }
        }
        public static bool IsProjectInSolution(DTE dteObject, string fullName)
        public static Project ProjectFromSolution(DTE dteObject, string fullName)
        {
            var fi = new FileInfo(fullName);
            ThreadHelper.ThrowIfNotOnUIThread();
            fullName = new FileInfo(fullName).FullName;
            foreach (var p in ProjectsInSolution(dteObject)) {
                if (p.FullName.ToLower() == fi.FullName.ToLower())
                    return true;
                if (p.FullName.Equals(fullName, StringComparison.OrdinalIgnoreCase))
                    return p;
            }
            return false;
            return null;
        }
        /// <summary>
        /// Returns the normalized file path of a given file.
        /// </summary>
        /// <param name="name">file name</param>
        static public string NormalizeFilePath(string name)
        public static string NormalizeFilePath(string name)
        {
            var fi = new FileInfo(name);
            return fi.FullName;
        }
        static public string NormalizeRelativeFilePath(string path)
        public static string NormalizeRelativeFilePath(string path)
        {
            if (path == null)
                return ".\\";
@@ -299,7 +227,7 @@
            return path;
        }
        static public bool IsAbsoluteFilePath(string path)
        public static bool IsAbsoluteFilePath(string path)
        {
            path = path.Trim();
            if (path.Length >= 2 && path[1] == ':')
@@ -316,7 +244,7 @@
        /// </summary>
        /// <param name="streamReader"></param>
        /// <returns>the composite string</returns>
        static private string ReadProFileLine(StreamReader streamReader)
        private static string ReadProFileLine(StreamReader streamReader)
        {
            var line = streamReader.ReadLine();
            if (line == null)
@@ -337,7 +265,7 @@
        /// </summary>
        /// <param name="profile">full name of .pro file to read</param>
        /// <returns>true if this is a subdirs file</returns>
        static public bool IsSubDirsFile(string profile)
        public static bool IsSubDirsFile(string profile)
        {
            StreamReader sr = null;
            try {
@@ -407,55 +335,6 @@
        }
        /// <summary>
        /// Replaces a string in the commandLine, description, outputs and additional dependencies
        /// in all Custom build tools of the project
        /// </summary>
        /// <param name="project">Project</param>
        /// <param name="oldString">String, which is going to be replaced</param>
        /// <param name="oldString">String, which is going to replace the other one</param>
        /// <returns></returns>
        public static void ReplaceInCustomBuildTools(Project project, string oldString, string replaceString)
        {
            var vcPro = (VCProject)project.Object;
            if (vcPro == null)
                return;
            var qtMsBuild = new QtMsBuildContainer(new VCPropertyStorageProvider());
            qtMsBuild.BeginSetItemProperties();
            foreach (VCFile vcfile in (IVCCollection)vcPro.Files) {
                foreach (VCFileConfiguration config in (IVCCollection)vcfile.FileConfigurations) {
                    try {
                        if (vcfile.ItemType == "CustomBuild") {
                            var tool = GetCustomBuildTool(config);
                            if (tool == null)
                                continue;
                            tool.CommandLine = tool.CommandLine
                                .Replace(oldString, replaceString,
                                StringComparison.OrdinalIgnoreCase);
                            tool.Description = tool.Description
                                .Replace(oldString, replaceString,
                                StringComparison.OrdinalIgnoreCase);
                            tool.Outputs = tool.Outputs
                                .Replace(oldString, replaceString,
                                StringComparison.OrdinalIgnoreCase);
                            tool.AdditionalDependencies = tool.AdditionalDependencies
                                .Replace(oldString, replaceString,
                                StringComparison.OrdinalIgnoreCase);
                        } else {
                            var tool = new QtCustomBuildTool(config, qtMsBuild);
                            tool.CommandLine = tool.CommandLine
                                .Replace(oldString, replaceString,
                                StringComparison.OrdinalIgnoreCase);
                        }
                    } catch (Exception) {
                    }
                }
            }
            qtMsBuild.EndSetItemProperties();
        }
        /// <summary>
        /// Since VS2010 it is possible to have VCCustomBuildTools without commandlines
        /// for certain filetypes. We are not interested in them and thus try to read the
        /// tool's commandline. If this causes an exception, we ignore it.
@@ -463,23 +342,19 @@
        /// </summary>
        /// <param name="config">File configuration</param>
        /// <returns></returns>
        static public VCCustomBuildTool GetCustomBuildTool(VCFileConfiguration config)
        public static VCCustomBuildTool GetCustomBuildTool(VCFileConfiguration config)
        {
            var file = config.File as VCFile;
            if (file == null || file.ItemType != "CustomBuild")
                return null;
            var tool = config.Tool as VCCustomBuildTool;
            if (tool == null)
                return null;
            try {
                // TODO: The return value is not used at all?
                var cmdLine = tool.CommandLine;
            } catch {
                return null;
            if (config.File is VCFile file
                && file.ItemType == "CustomBuild"
                && config.Tool is VCCustomBuildTool tool) {
                    try {
                        _ = tool.CommandLine;
                    } catch {
                        return null;
                    }
                    return tool;
            }
            return tool;
            return null;
        }
        /// <summary>
@@ -488,8 +363,10 @@
        /// has to be "CustomBuild"
        /// </summary>
        /// <param name="projectItem">Project Item which needs to have custom build tool</param>
        static public void EnsureCustomBuildToolAvailable(ProjectItem projectItem)
        public static void EnsureCustomBuildToolAvailable(ProjectItem projectItem)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            foreach (Property prop in projectItem.Properties) {
                if (prop.Name == "ItemType") {
                    if ((string)prop.Value != "CustomBuild")
@@ -499,169 +376,10 @@
            }
        }
        /// <summary>
        /// As Qmake -tp vc Adds the full path to the additional dependencies
        /// we need to do the same when toggling project kind to qmake generated.
        /// </summary>
        /// <returns></returns>
        private static string AddFullPathToAdditionalDependencies(string qtDir, string additionalDependencies)
        {
            var returnString = additionalDependencies;
            returnString =
                Regex.Replace(returnString, "Qt(\\S+5?)\\.lib", qtDir + "\\lib\\Qt${1}.lib");
            returnString =
                Regex.Replace(returnString, "(qtmaind?5?)\\.lib", qtDir + "\\lib\\${1}.lib");
            returnString =
                Regex.Replace(returnString, "(enginiod?5?)\\.lib", qtDir + "\\lib\\${1}.lib");
            return returnString;
        }
        /// <summary>
        /// Toggles the kind of a project. If the project is a QMake generated project (qmake -tp vc)
        /// it is transformed to an Qt VS Tools project and vice versa.
        /// </summary>
        /// <param name="project">Project</param>
        /// <returns></returns>
        public static void ToggleProjectKind(Project project)
        {
            if (QtProject.GetFormatVersion(project) >= Resources.qtMinFormatVersion_Settings)
                return;
            string qtDir = null;
            var vcPro = (VCProject)project.Object;
            if (!IsQMakeProject(project))
                return;
            if (IsQtProject(project)) {
                // TODO: qtPro is never used.
                var qtPro = QtProject.Create(project);
                var vm = QtVersionManager.The();
                qtDir = vm.GetInstallPath(project);
                foreach (var global in (string[])project.Globals.VariableNames) {
                    if (global.StartsWith("Qt5Version", StringComparison.Ordinal))
                        project.Globals.set_VariablePersists(global, false);
                }
                foreach (VCConfiguration config in (IVCCollection)vcPro.Configurations) {
                    var compiler = CompilerToolWrapper.Create(config);
                    var linker = (VCLinkerTool)((IVCCollection)config.Tools).Item("VCLinkerTool");
                    var librarian = (VCLibrarianTool)((IVCCollection)config.Tools).Item("VCLibrarianTool");
                    if (compiler != null) {
                        var additionalIncludes = compiler.GetAdditionalIncludeDirectories();
                        additionalIncludes = additionalIncludes.Replace("$(QTDIR)", qtDir,
                            StringComparison.OrdinalIgnoreCase);
                        compiler.SetAdditionalIncludeDirectories(additionalIncludes);
                    }
                    if (linker != null) {
                        linker.AdditionalLibraryDirectories = linker.AdditionalLibraryDirectories.
                            Replace("$(QTDIR)", qtDir, StringComparison.OrdinalIgnoreCase);
                        linker.AdditionalDependencies = AddFullPathToAdditionalDependencies(qtDir, linker.AdditionalDependencies);
                    } else {
                        librarian.AdditionalLibraryDirectories = librarian.AdditionalLibraryDirectories
                            .Replace("$(QTDIR)", qtDir, StringComparison.OrdinalIgnoreCase);
                        librarian.AdditionalDependencies = AddFullPathToAdditionalDependencies(qtDir, librarian.AdditionalDependencies);
                    }
                }
                ReplaceInCustomBuildTools(project, "$(QTDIR)", qtDir);
            } else {
                qtDir = GetQtDirFromQMakeProject(project);
                var vm = QtVersionManager.The();
                var qtVersion = vm.GetQtVersionFromInstallDir(qtDir);
                if (qtVersion == null)
                    qtVersion = vm.GetDefaultVersion();
                if (qtDir == null)
                    qtDir = vm.GetInstallPath(qtVersion);
                var vi = vm.GetVersionInfo(qtVersion);
                var platformName = vi.GetVSPlatformName();
                vm.SaveProjectQtVersion(project, qtVersion, platformName);
                var qtPro = QtProject.Create(project);
                if (!qtPro.SelectSolutionPlatform(platformName) || !qtPro.HasPlatform(platformName)) {
                    var newProject = false;
                    qtPro.CreatePlatform("Win32", platformName, null, vi, ref newProject);
                    if (!qtPro.SelectSolutionPlatform(platformName))
                        Messages.Print("Can't select the platform " + platformName + ".");
                }
                var activeConfig = project.ConfigurationManager.ActiveConfiguration.ConfigurationName;
                var activeVCConfig = (VCConfiguration)((IVCCollection)qtPro.VCProject.Configurations).Item(activeConfig);
                if (activeVCConfig.ConfigurationType == ConfigurationTypes.typeDynamicLibrary) {
                    var compiler = CompilerToolWrapper.Create(activeVCConfig);
                    var linker = (VCLinkerTool)((IVCCollection)activeVCConfig.Tools).Item("VCLinkerTool");
                    var ppdefs = compiler.GetPreprocessorDefinitions();
                    if (ppdefs != null
                        && ppdefs.IndexOf("QT_PLUGIN", StringComparison.Ordinal) > -1
                        && ppdefs.IndexOf("QDESIGNER_EXPORT_WIDGETS", StringComparison.Ordinal) > -1
                        && ppdefs.IndexOf("QtDesigner", StringComparison.Ordinal) > -1
                        && linker.AdditionalDependencies != null
                        && linker.AdditionalDependencies.IndexOf("QtDesigner", StringComparison.Ordinal) > -1) {
                        qtPro.MarkAsDesignerPluginProject();
                    }
                }
                CleanupQMakeDependencies(project);
                foreach (VCConfiguration config in (IVCCollection)vcPro.Configurations) {
                    var compiler = CompilerToolWrapper.Create(config);
                    var linker = (VCLinkerTool)((IVCCollection)config.Tools).Item("VCLinkerTool");
                    if (compiler != null) {
                        var additionalIncludes = compiler.AdditionalIncludeDirectories;
                        if (additionalIncludes != null) {
                            ReplaceDirectory(ref additionalIncludes, qtDir, "$(QTDIR)", project);
                            compiler.AdditionalIncludeDirectories = additionalIncludes;
                        }
                    }
                    if (linker != null) {
                        var linkerToolWrapper = new LinkerToolWrapper(linker);
                        var paths = linkerToolWrapper.AdditionalLibraryDirectories;
                        if (paths != null) {
                            ReplaceDirectory(ref paths, qtDir, "$(QTDIR)", project);
                            linkerToolWrapper.AdditionalLibraryDirectories = paths;
                        }
                    }
                }
                ReplaceInCustomBuildTools(project, qtDir, "$(QTDIR)");
                qtPro.TranslateFilterNames();
            }
            project.Save(project.FullName);
        }
        /// <summary>
        /// Replaces every occurrence of oldDirectory with replacement in the array of strings.
        /// Parameter oldDirectory must be an absolute path.
        /// This function converts relative directories to absolute paths internally
        /// and replaces them, if necessary. If no replacement is done, the path isn't altered.
        /// </summary>
        /// <param name="project">The project is needed to convert relative paths to absolute paths.</param>
        private static void ReplaceDirectory(ref List<string> paths, string oldDirectory, string replacement, Project project)
        {
            for (var i = 0; i < paths.Count; ++i) {
                var dirName = paths[i];
                if (dirName.StartsWith("\"", StringComparison.Ordinal) && dirName.EndsWith("\"", StringComparison.Ordinal)) {
                    dirName = dirName.Substring(1, dirName.Length - 2);
                }
                if (!Path.IsPathRooted(dirName)) {
                    // convert to absolute path
                    dirName = Path.Combine(Path.GetDirectoryName(project.FullName), dirName);
                    dirName = Path.GetFullPath(dirName);
                    var alteredDirName = dirName.Replace(oldDirectory, replacement, StringComparison
                        .OrdinalIgnoreCase);
                    if (alteredDirName == dirName)
                        continue;
                    dirName = alteredDirName;
                } else {
                    dirName = dirName.Replace(oldDirectory, replacement, StringComparison
                        .OrdinalIgnoreCase);
                }
                paths[i] = dirName;
            }
        }
        public static string GetQtDirFromQMakeProject(Project project)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            var vcProject = project.Object as VCProject;
            if (vcProject == null)
                return null;
@@ -742,13 +460,25 @@
        }
        /// <summary>
        /// Return true if the project is a Qt project, otherwise false.
        /// Return true if the project is a VS tools project; false otherwise.
        /// </summary>
        /// <param name="proj">project</param>
        /// <returns></returns>
        public static bool IsQtProject(VCProject proj)
        public static bool IsVsToolsProject(Project proj)
        {
            if (!IsQMakeProject(proj))
            ThreadHelper.ThrowIfNotOnUIThread(); // C++ Project Type GUID
            if (proj == null || proj.Kind != "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")
                return false;
            return IsVsToolsProject(proj.Object as VCProject);
        }
        /// <summary>
        /// Return true if the project is a VS tools project; false otherwise.
        /// </summary>
        /// <param name="proj">project</param>
        public static bool IsVsToolsProject(VCProject proj)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (!IsQtProject(proj))
                return false;
            if (QtProject.GetFormatVersion(proj) >= Resources.qtMinFormatVersion_Settings)
@@ -759,95 +489,39 @@
                return false;
            foreach (var global in envPro.Globals.VariableNames as string[]) {
                if (global.StartsWith("Qt5Version", StringComparison.Ordinal) && envPro.Globals.get_VariablePersists(global))
                if (global.StartsWith("Qt5Version", StringComparison.Ordinal)
                    && envPro.Globals.get_VariablePersists(global)) {
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Returns true if the specified project is a Qt Project.
        /// Return true if the project is a Qt project; false otherwise.
        /// </summary>
        /// <param name="proj">project</param>
        public static bool IsQtProject(Project proj)
        {
            try {
                if (proj != null && proj.Kind == "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")
                    return IsQtProject(proj.Object as VCProject);
            } catch { }
            return false;
            ThreadHelper.ThrowIfNotOnUIThread(); //C++ Project Type GUID
            if (proj == null || proj.Kind != "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")
                return false;
            return IsQtProject(proj.Object as VCProject);
        }
        /// <summary>
        /// Return true if the project is a QMake -tp vc project, otherwise false.
        /// Return true if the project is a Qt project; false otherwise.
        /// </summary>
        /// <param name="proj">project</param>
        /// <returns></returns>
        public static bool IsQMakeProject(VCProject proj)
        public static bool IsQtProject(VCProject proj)
        {
            if (proj == null)
                return false;
            var keyword = proj.keyword;
            if (keyword == null ||
                (!keyword.StartsWith(Resources.qtProjectV2Keyword, StringComparison.Ordinal)
                && !keyword.StartsWith(Resources.qtProjectKeyword, StringComparison.Ordinal))) {
            if (string.IsNullOrEmpty(keyword))
                return false;
            }
            return true;
        }
        /// <summary>
        /// Returns true if the specified project is a QMake -tp vc Project.
        /// </summary>
        /// <param name="proj">project</param>
        public static bool IsQMakeProject(Project proj)
        {
            try {
                if (proj != null && proj.Kind == "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")
                    return IsQMakeProject(proj.Object as VCProject);
            } catch { }
            return false;
        }
        public static void CleanupQMakeDependencies(Project project)
        {
            var vcPro = (VCProject)project.Object;
            // clean up qmake mess
            var rxp1 = new Regex("\\bQt\\w+d?5?\\.lib\\b");
            var rxp2 = new Regex("\\bQAx\\w+\\.lib\\b");
            var rxp3 = new Regex("\\bqtmaind?.lib\\b");
            var rxp4 = new Regex("\\benginiod?.lib\\b");
            foreach (VCConfiguration cfg in (IVCCollection)vcPro.Configurations) {
                var linker = (VCLinkerTool)((IVCCollection)cfg.Tools).Item("VCLinkerTool");
                if (linker == null || linker.AdditionalDependencies == null)
                    continue;
                var linkerWrapper = new LinkerToolWrapper(linker);
                var deps = linkerWrapper.AdditionalDependencies;
                var newDeps = new List<string>();
                foreach (var lib in deps) {
                    var m1 = rxp1.Match(lib);
                    var m2 = rxp2.Match(lib);
                    var m3 = rxp3.Match(lib);
                    var m4 = rxp4.Match(lib);
                    if (m1.Success)
                        newDeps.Add(m1.ToString());
                    else if (m2.Success)
                        newDeps.Add(m2.ToString());
                    else if (m3.Success)
                        newDeps.Add(m3.ToString());
                    else if (m4.Success)
                        newDeps.Add(m4.ToString());
                    else
                        newDeps.Add(lib);
                }
                // Remove Duplicates
                var uniques = new Dictionary<string, int>();
                foreach (var dep in newDeps)
                    uniques[dep] = 1;
                var uniqueList = new List<string>(uniques.Keys);
                linkerWrapper.AdditionalDependencies = uniqueList;
            }
            return keyword.StartsWith(Resources.qtProjectKeyword, StringComparison.Ordinal)
                || keyword.StartsWith(Resources.qtProjectV2Keyword, StringComparison.Ordinal);
        }
        /// <summary>
@@ -951,6 +625,30 @@
            }
        }
        /// <summary>
        /// Converts all directory separators of the path to the alternate character
        /// directory separator. For instance, FromNativeSeparators("c:\\winnt\\system32")
        /// returns "c:/winnt/system32".
        /// </summary>
        /// <param name="path">The path to convert.</param>
        /// <returns>Returns path using '/' as file separator.</returns>
        public static string FromNativeSeparators(string path)
        {
            return path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        }
        /// <summary>
        /// Converts all alternate directory separators characters of the path to the native
        /// directory separator. For instance, ToNativeSeparators("c:/winnt/system32")
        /// returns "c:\\winnt\\system32".
        /// </summary>
        /// <param name="path">The path to convert.</param>
        /// <returns>Returns path using '\' as file separator.</returns>
        public static string ToNativeSeparator(string path)
        {
            return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
        }
        public static string ChangePathFormat(string path)
        {
            return path.Replace('\\', '/');
@@ -981,6 +679,8 @@
        public static void CollapseFilter(UIHierarchyItem item, UIHierarchy hierarchy, string nodeToCollapseFilter)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (string.IsNullOrEmpty(nodeToCollapseFilter))
                return;
@@ -994,6 +694,8 @@
        public static void CollapseFilter(UIHierarchyItem item, UIHierarchy hierarchy)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            var subItems = item.UIHierarchyItems;
            if (subItems != null) {
                foreach (UIHierarchyItem innerItem in subItems) {
@@ -1042,7 +744,7 @@
        public static List<string> GetProjectFiles(Project pro, FilesToList filter)
        {
            var fileList = new List<string>();
            ThreadHelper.ThrowIfNotOnUIThread();
            VCProject vcpro;
            try {
@@ -1052,6 +754,7 @@
                return null;
            }
            var fileList = new List<string>();
            var configurationName = pro.ConfigurationManager.ActiveConfiguration.ConfigurationName;
            foreach (VCFile vcfile in (IVCCollection)vcpro.Files) {
@@ -1120,21 +823,24 @@
        /// <param name="fileName"></param>
        public static void RemoveFileInProject(VCProject vcpro, string fileName)
        {
            var qtProj = QtProject.Create(vcpro);
            var fi = new FileInfo(fileName);
            ThreadHelper.ThrowIfNotOnUIThread();
            fileName = new FileInfo(fileName).FullName;
            foreach (VCFile vcfile in (IVCCollection)vcpro.Files) {
                if (vcfile.FullPath.ToLower() == fi.FullName.ToLower()) {
                if (vcfile.FullPath.Equals(fileName, StringComparison.OrdinalIgnoreCase)) {
                    vcpro.RemoveFile(vcfile);
                    qtProj.MoveFileToDeletedFolder(vcfile);
                    QtProject.Create(vcpro)?.MoveFileToDeletedFolder(vcfile);
                }
            }
        }
        public static Project GetSelectedProject(DTE dteObject)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (dteObject == null)
                return null;
            Array prjs = null;
            try {
                prjs = (Array)dteObject.ActiveSolutionProjects;
@@ -1146,30 +852,23 @@
                return null;
            // don't handle multiple selection... use the first one
            if (prjs.GetValue(0) is Project)
                return (Project)prjs.GetValue(0);
            if (prjs.GetValue(0) is Project project)
                return project;
            return null;
        }
        public static Project GetActiveDocumentProject(DTE dteObject)
        {
            if (dteObject == null)
                return null;
            var doc = dteObject.ActiveDocument;
            if (doc == null)
                return null;
            if (doc.ProjectItem == null)
                return null;
            return doc.ProjectItem.ContainingProject;
            ThreadHelper.ThrowIfNotOnUIThread();
            return dteObject?.ActiveDocument?.ProjectItem?.ContainingProject;
        }
        public static Project GetSingleProjectInSolution(DTE dteObject)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            var projectList = ProjectsInSolution(dteObject);
            if (dteObject == null || dteObject.Solution == null ||
                    projectList.Count != 1)
            if (projectList.Count != 1)
                return null; // no way to know which one to select
            return projectList[0];
@@ -1182,22 +881,24 @@
        /// </summary>
        public static Project GetSelectedQtProject(DTE dteObject)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            // can happen sometimes shortly after starting VS
            if (dteObject == null || dteObject.Solution == null
                || ProjectsInSolution(dteObject).Count == 0)
            if (ProjectsInSolution(dteObject).Count == 0)
                return null;
            Project pro;
            if ((pro = GetSelectedProject(dteObject)) == null) {
            var pro = GetSelectedProject(dteObject);
            if (pro == null) {
                if ((pro = GetSingleProjectInSolution(dteObject)) == null)
                    pro = GetActiveDocumentProject(dteObject);
            }
            return IsQtProject(pro) ? pro : null;
            return IsVsToolsProject(pro) ? pro : null;
        }
        public static VCFile[] GetSelectedFiles(DTE dteObject)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (GetSelectedQtProject(dteObject) == null)
                return null;
@@ -1239,6 +940,8 @@
        public static RccOptions ParseRccOptions(string cmdLine, VCFile qrcFile)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            var pro = VCProjectToProject((VCProject)qrcFile.project);
            var rccOpts = new RccOptions(pro, qrcFile);
@@ -1261,11 +964,17 @@
        public static Project VCProjectToProject(VCProject vcproj)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            return (Project)vcproj.Object;
        }
        public static List<Project> ProjectsInSolution(DTE dteObject)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (dteObject == null)
                return new List<Project>();
            var projects = new List<Project>();
            var solution = dteObject.Solution;
            if (solution != null) {
@@ -1287,6 +996,8 @@
        private static void addSubProjects(Project prj, ref List<Project> projects)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            // If the actual object of the project is null then the project was probably unloaded.
            if (prj.Object == null)
                return;
@@ -1303,6 +1014,8 @@
        private static void addSubProjects(ProjectItems subItems, ref List<Project> projects)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (subItems == null)
                return;
@@ -1393,21 +1106,6 @@
            return true;
        }
        public static string FindFileInPATH(string fileName)
        {
            var envPATH = Environment.ExpandEnvironmentVariables("%PATH%");
            var directories = envPATH.Split(';');
            foreach (var directory in directories) {
                var fullFilePath = directory;
                if (!fullFilePath.EndsWith("\\", StringComparison.Ordinal))
                    fullFilePath += '\\';
                fullFilePath += fileName;
                if (File.Exists(fullFilePath))
                    return fullFilePath;
            }
            return null;
        }
        /// <summary>
        /// This method copies the specified directory and all its child directories and files to
        /// the specified destination. The destination directory is created if it does not exist.
@@ -1453,13 +1151,14 @@
            string platformName,
            string filePath = null)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            if (project == null
                || string.IsNullOrEmpty(configName)
                || string.IsNullOrEmpty(platformName))
                return false;
            var vcProject = project.Object as VCProject;
            if (filePath == null) {
                var vcConfig = (from VCConfiguration _config
                                in (IVCCollection)vcProject.Configurations
@@ -1503,12 +1202,10 @@
            VCProject vcProj = null;
            VCFile vcFile = null;
            string configName = "", platformName = "";
            var vcConfig = config as VCConfiguration;
            if (vcConfig != null) {
            if (config is VCConfiguration vcConfig) {
                vcProj = vcConfig.project as VCProject;
                configName = vcConfig.ConfigurationName;
                var vcPlatform = vcConfig.Platform as VCPlatform;
                if (vcPlatform != null)
                if (vcConfig.Platform is VCPlatform vcPlatform)
                    platformName = vcPlatform.Name;
                try {
                    expanded = vcConfig.Evaluate(expanded);
@@ -1520,11 +1217,9 @@
                vcFile = vcFileConfig.File as VCFile;
                if (vcFile != null)
                    vcProj = vcFile.project as VCProject;
                var vcProjConfig = vcFileConfig.ProjectConfiguration as VCConfiguration;
                if (vcProjConfig != null) {
                if (vcFileConfig.ProjectConfiguration is VCConfiguration vcProjConfig) {
                    configName = vcProjConfig.ConfigurationName;
                    var vcPlatform = vcProjConfig.Platform as VCPlatform;
                    if (vcPlatform != null)
                    if (vcProjConfig.Platform is VCPlatform vcPlatform)
                        platformName = vcPlatform.Name;
                }
                try {
@@ -1637,6 +1332,7 @@
            return true;
        }
#if VS2017
        private static string GetRegistrySoftwareString(string subKeyName, string valueName)
        {
            var keyName = new StringBuilder();
@@ -1644,28 +1340,25 @@
            if (System.Environment.Is64BitOperatingSystem && IntPtr.Size == 4)
                keyName.Append(@"WOW6432Node\");
            keyName.Append(subKeyName);
            try {
                using (var key = Registry.LocalMachine.OpenSubKey(keyName.ToString(), false)) {
                    if (key == null)
                        return ""; //key not found
                    RegistryValueKind valueKind = key.GetValueKind(valueName);
                    if (valueKind != RegistryValueKind.String
                        && valueKind != RegistryValueKind.ExpandString) {
                        return ""; //wrong value kind
                    }
                    Object objValue = key.GetValue(valueName);
                    if (objValue == null)
                        return ""; //error getting value
                    return objValue.ToString();
                }
            } catch {
                return "";
            }
        }
#endif
        public static string GetWindows10SDKVersion()
        {
@@ -1713,11 +1406,6 @@
            string vcPath = Path.Combine(vsPath, "VC");
#endif
            return vcPath;
        }
        public static bool SetVCVars(ProcessStartInfo startInfo)
        {
            return SetVCVars(null, startInfo);
        }
        public static bool SetVCVars(VersionInformation VersionInfo, ProcessStartInfo startInfo)
@@ -1820,8 +1508,21 @@
                    }
                }
            }
            // Get PATH
            string envPath = startInfo.EnvironmentVariables["PATH"];
            string clPath = envPath.Split(';')
            // Remove invalid chars
            envPath = string.Join("", envPath.Split(Path.GetInvalidPathChars()));
            // Split into list of paths
            var paths = envPath
                .Split(';')
                .Where(x => !string.IsNullOrEmpty(x))
                .Select(x => x.Trim());
            // Check if cl.exe is in PATH
            string clPath = paths
                .Select(path => Path.Combine(path, "cl.exe"))
                .Where(pathToCl => File.Exists(pathToCl))
                .FirstOrDefault();
@@ -1872,12 +1573,6 @@
            } else {
                return canonicalPath;
            }
        }
        public static bool PathEquals(string path1, string path2)
        {
            return (CanonicalPath(path1).Equals(CanonicalPath(path2),
                StringComparison.InvariantCultureIgnoreCase));
        }
        public static bool PathIsRelativeTo(string path, string subPath)