/****************************************************************************
**
** Copyright (C) 2022 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.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.VCProjectEngine;
using Microsoft.Win32;
namespace QtVsTools.Core
{
///
/// Summary description for QtVersionManager.
///
public class QtVersionManager
{
private static QtVersionManager instance;
private readonly string regVersionPath;
private readonly string strVersionKey;
private Hashtable versionCache;
protected QtVersionManager()
{
strVersionKey = "Versions";
regVersionPath = Resources.registryVersionPath;
}
private static readonly EventWaitHandle packageInit = new EventWaitHandle(false, EventResetMode.ManualReset);
private static EventWaitHandle packageInitDone = null;
public static QtVersionManager The(EventWaitHandle initDone = null)
{
if (initDone == null) {
packageInit.WaitOne();
packageInitDone.WaitOne();
} else {
packageInitDone = initDone;
packageInit.Set();
}
if (instance == null)
instance = new QtVersionManager();
return instance;
}
public VersionInformation GetVersionInfo(string name)
{
if (name == null)
return null;
if (name == "$(DefaultQtVersion)")
name = GetDefaultVersion();
if (versionCache == null)
versionCache = new Hashtable();
var vi = versionCache[name] as VersionInformation;
if (vi == null) {
var qtdir = GetInstallPath(name);
versionCache[name] = vi = VersionInformation.Get(qtdir);
if (vi != null)
vi.name = name;
}
return vi;
}
public VersionInformation GetVersionInfo(EnvDTE.Project project)
{
ThreadHelper.ThrowIfNotOnUIThread();
return GetVersionInfo(GetProjectQtVersion(project));
}
public string[] GetVersions()
{
return GetVersions(Registry.CurrentUser);
}
public string GetQtVersionFromInstallDir(string qtDir)
{
if (qtDir == null)
return null;
var versions = GetVersions();
foreach (var version in versions) {
var installPath = GetInstallPath(version);
if (installPath == null)
continue;
if (installPath.Equals(qtDir, StringComparison.OrdinalIgnoreCase))
return version;
}
return null;
}
public string[] GetVersions(RegistryKey root)
{
var key = root.OpenSubKey("SOFTWARE\\" + Resources.registryRootPath, false);
if (key == null)
return new string[] { };
var versionKey = key.OpenSubKey(strVersionKey, false);
if (versionKey == null)
return new string[] { };
return versionKey.GetSubKeyNames();
}
///
/// Check if all Qt versions are valid and readable.
///
///
/// true, if there are one or more invalid Qt version
public bool HasInvalidVersions(out string errorMessage, out bool defaultVersionInvalid)
{
var defaultVersion = GetDefaultVersionString();
defaultVersionInvalid = string.IsNullOrEmpty(defaultVersion);
errorMessage = null;
foreach (var version in GetVersions()) {
if (version == "$(DefaultQtVersion)")
continue;
var path = GetInstallPath(version);
if (path != null && (path.StartsWith("SSH:") || path.StartsWith("WSL:")))
continue;
if (string.IsNullOrEmpty(path) || !QMake.Exists(path)) {
errorMessage += version + " in " + path + "\n";
defaultVersionInvalid |= version == defaultVersion;
}
if (!string.IsNullOrEmpty(errorMessage)) {
errorMessage = "These Qt version are inaccessible:\n"
+ errorMessage
+ "Make sure that you have read access to all files in your Qt directories.";
}
}
return errorMessage != null;
}
public void SetLatestQtVersionAsDefault()
{
var validVersions = new Dictionary();
foreach (var version in GetVersions()) {
if (version == "$(DefaultQtVersion)")
continue;
var path = GetInstallPath(version);
if (!string.IsNullOrEmpty(path) && QMake.Exists(path))
validVersions[version] = new Version(new QtConfig(path).VersionString);
}
if (validVersions.Count <= 0)
return;
var defaultName = "";
Version defaultVersion = null;
foreach (var tmp in validVersions) {
var version = tmp.Value;
if (defaultVersion == null || defaultVersion < version) {
defaultName = tmp.Key;
defaultVersion = version;
}
}
SaveDefaultVersion(defaultName);
}
public string GetInstallPath(string version)
{
if (version == "$(DefaultQtVersion)")
version = GetDefaultVersion();
return GetInstallPath(version, Registry.CurrentUser);
}
public string GetInstallPath(string version, RegistryKey root)
{
if (version == "$(DefaultQtVersion)")
version = GetDefaultVersion(root);
if (version == "$(QTDIR)")
return Environment.GetEnvironmentVariable("QTDIR");
var key = root.OpenSubKey("SOFTWARE\\" + Resources.registryRootPath, false);
var versionKey = key?.OpenSubKey(strVersionKey + "\\" + version, false);
return versionKey?.GetValue("InstallDir") as string;
}
public string GetInstallPath(EnvDTE.Project project)
{
ThreadHelper.ThrowIfNotOnUIThread();
var version = GetProjectQtVersion(project);
if (version == "$(DefaultQtVersion)")
version = GetDefaultVersion();
if (version == null)
return null;
return GetInstallPath(version);
}
public bool SaveVersion(string versionName, string path, bool checkPath = true)
{
var verName = versionName?.Trim().Replace(@"\", "_");
if (string.IsNullOrEmpty(verName))
return false;
var dir = string.Empty;
if (verName != "$(QTDIR)") {
DirectoryInfo di;
try {
di = new DirectoryInfo(path);
} catch {
di = null;
}
if (di?.Exists == true) {
dir = di.FullName;
} else if (!checkPath) {
dir = path;
} else {
return false;
}
}
string rootKeyPath = "SOFTWARE\\" + Resources.registryRootPath;
string versionKeyPath = strVersionKey + "\\" + verName;
using (var key = Registry.CurrentUser.CreateSubKey(rootKeyPath)) {
if (key == null) {
Messages.Print(
"ERROR: root registry key creation failed");
return false;
}
using (var versionKey = key.CreateSubKey(versionKeyPath)) {
if (versionKey == null) {
Messages.Print(
"ERROR: version registry key creation failed");
return false;
}
versionKey.SetValue("InstallDir", dir);
}
}
return true;
}
public void RemoveVersion(string versionName)
{
var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\" + regVersionPath, true);
if (key == null)
return;
key.DeleteSubKey(versionName);
key.Close();
}
internal bool IsVersionAvailable(string version)
{
var versionAvailable = false;
var versions = GetVersions();
foreach (var ver in versions) {
if (version == ver) {
versionAvailable = true;
break;
}
}
return versionAvailable;
}
public bool SaveProjectQtVersion(EnvDTE.Project project, string version)
{
ThreadHelper.ThrowIfNotOnUIThread();
return SaveProjectQtVersion(project, version, project.ConfigurationManager.ActiveConfiguration.PlatformName);
}
public bool SaveProjectQtVersion(EnvDTE.Project project, string version, string platform)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (!IsVersionAvailable(version) && version != "$(DefaultQtVersion)")
return false;
if (QtProject.GetFormatVersion(project) >= Resources.qtMinFormatVersion_Settings) {
var vcPro = project.Object as VCProject;
if (vcPro == null)
return false;
foreach (VCConfiguration3 config in (IVCCollection)vcPro.Configurations) {
config.SetPropertyValue(Resources.projLabelQtSettings, true,
"QtInstall", version);
}
return true;
}
var key = "Qt5Version " + platform;
if (!project.Globals.get_VariableExists(key) || project.Globals[key].ToString() != version)
project.Globals[key] = version;
if (!project.Globals.get_VariablePersists(key))
project.Globals.set_VariablePersists(key, true);
return true;
}
public string GetProjectQtVersion(EnvDTE.Project project)
{
ThreadHelper.ThrowIfNotOnUIThread();
EnvDTE.Configuration config = null;
try {
config = project.ConfigurationManager.ActiveConfiguration;
} catch {
// Accessing the ActiveConfiguration property throws an exception
// if there's an "unconfigured" platform in the Solution platform combo box.
config = project.ConfigurationManager.Item(1);
}
var version = GetProjectQtVersion(project, config);
if (version == null && project.Globals.get_VariablePersists("Qt5Version")) {
version = (string)project.Globals["Qt5Version"];
ExpandEnvironmentVariablesInQtVersion(ref version);
return VerifyIfQtVersionExists(version) ? version : null;
}
if (version == null)
version = Legacy.QtVersionManager.GetSolutionQtVersion(project.DTE.Solution);
return version;
}
public string GetProjectQtVersion(EnvDTE.Project project, EnvDTE.Configuration config)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (QtProject.GetFormatVersion(project) >= Resources.qtMinFormatVersion_Settings)
return QtProject.GetPropertyValue(project, config, "QtInstall");
var key = "Qt5Version " + config.PlatformName;
if (!project.Globals.get_VariablePersists(key))
return null;
var version = (string)project.Globals[key];
ExpandEnvironmentVariablesInQtVersion(ref version);
return VerifyIfQtVersionExists(version) ? version : null;
}
public string GetProjectQtVersion(EnvDTE.Project project, string platform)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (QtProject.GetFormatVersion(project) >= Resources.qtMinFormatVersion_Settings)
return GetProjectQtVersion(project);
var key = "Qt5Version " + platform;
if (!project.Globals.get_VariablePersists(key))
return null;
var version = (string)project.Globals[key];
ExpandEnvironmentVariablesInQtVersion(ref version);
return VerifyIfQtVersionExists(version) ? version : null;
}
private static void ExpandEnvironmentVariablesInQtVersion(ref string version)
{
if (version != "$(QTDIR)" && version != "$(DefaultQtVersion)") {
// Make it possible to specify the version name
// via an environment variable
var regExp =
new System.Text.RegularExpressions.Regex("\\$\\((?\\S+)\\)");
var match = regExp.Match(version);
if (match.Success) {
var env = match.Groups["VarName"].Value;
version = Environment.GetEnvironmentVariable(env);
}
}
}
public string GetDefaultVersion()
{
return GetDefaultVersion(Registry.CurrentUser);
}
public string GetDefaultVersion(RegistryKey root)
{
string defaultVersion = null;
try {
var key = root.OpenSubKey("SOFTWARE\\" + regVersionPath, false);
if (key != null)
defaultVersion = (string)key.GetValue("DefaultQtVersion");
} catch {
Messages.DisplayWarningMessage(SR.GetString("QtVersionManager_CannotLoadQtVersion"));
}
if (defaultVersion == null) {
MergeVersions();
var key = root.OpenSubKey("SOFTWARE\\" + regVersionPath, false);
if (key != null) {
var versions = GetVersions();
if (versions != null && versions.Length > 0)
defaultVersion = versions[versions.Length - 1];
if (defaultVersion != null)
SaveDefaultVersion(defaultVersion);
}
if (defaultVersion == null) {
// last fallback... try QTDIR
var qtDir = Environment.GetEnvironmentVariable("QTDIR");
if (qtDir == null)
return null;
var d = new DirectoryInfo(qtDir);
SaveVersion(d.Name, d.FullName);
if (SaveDefaultVersion(d.Name))
defaultVersion = d.Name;
}
}
return VerifyIfQtVersionExists(defaultVersion) ? defaultVersion : null;
}
public string GetDefaultVersionString()
{
string defaultVersion = null;
try {
var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\" + regVersionPath, false);
if (key != null)
defaultVersion = key.GetValue("DefaultQtVersion") as string;
} catch {
Messages.Print("Cannot read the default Qt version from registry.");
}
if (defaultVersion == null) {
var qtDir = Environment.GetEnvironmentVariable("QTDIR");
if (string.IsNullOrEmpty(qtDir))
return defaultVersion;
}
return defaultVersion;
}
public bool SaveDefaultVersion(string version)
{
if (version == "$(DefaultQtVersion)")
return false;
var key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\" + regVersionPath);
if (key == null)
return false;
key.SetValue("DefaultQtVersion", version);
return true;
}
private void MergeVersions()
{
var hkcuVersions = GetVersions();
var hklmVersions = GetVersions(Registry.LocalMachine);
var hkcuInstDirs = new string[hkcuVersions.Length];
for (var i = 0; i < hkcuVersions.Length; ++i)
hkcuInstDirs[i] = GetInstallPath(hkcuVersions[i]);
var hklmInstDirs = new string[hklmVersions.Length];
for (var i = 0; i < hklmVersions.Length; ++i)
hklmInstDirs[i] = GetInstallPath(hklmVersions[i], Registry.LocalMachine);
for (var i = 0; i < hklmVersions.Length; ++i) {
if (hklmInstDirs[i] == null)
continue;
var found = false;
for (var j = 0; j < hkcuInstDirs.Length; ++j) {
if (hkcuInstDirs[j] != null
&& hkcuInstDirs[j].ToLower() == hklmInstDirs[i].ToLower()) {
found = true;
break;
}
}
if (!found) {
for (var j = 0; j < hkcuVersions.Length; ++j) {
if (hkcuVersions[j] != null
&& hkcuVersions[j] == hklmVersions[i]) {
found = true;
break;
}
}
if (!found)
SaveVersion(hklmVersions[i], hklmInstDirs[i]);
}
}
}
internal bool VerifyIfQtVersionExists(string version)
{
if (version == "$(DefaultQtVersion)")
version = GetDefaultVersion();
if (!string.IsNullOrEmpty(version)) {
var regExp =
new System.Text.RegularExpressions.Regex("\\$\\(.*\\)");
if (regExp.IsMatch(version))
return true;
return Directory.Exists(GetInstallPath(version));
}
return false;
}
}
}