// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TaskStatusCenter; using Microsoft.VisualStudio.VCProjectEngine; using Task = System.Threading.Tasks.Task; namespace QtVsTools.Core.MsBuild { using Options; using QtVsTools.Common; using VisualStudio; using static Common.Utils; /// /// QtProject holds the Qt specific properties for a Visual Studio project. /// There exists at most one QtProject perVCProject. Use QtProject.GetOrAdd /// to get the QtProject for a VCProject. /// public partial class MsBuildProject : Concurrent { private static LazyFactory StaticLazy { get; } = new(); private static ConcurrentDictionary Instances => StaticLazy.Get(() => Instances, () => new ConcurrentDictionary()); private static IVsTaskStatusCenterService StatusCenter => StaticLazy.Get(() => StatusCenter, VsServiceProvider .GetService); public static MsBuildProject GetOrAdd(VCProject vcProject) { if (vcProject == null) return null; lock (StaticCriticalSection) { if (MsBuildProjectFormat.GetVersion(vcProject) >= MsBuildProjectFormat.Version.V3) { if (Instances.TryGetValue(vcProject.ProjectFile, out var project)) return project; project = new MsBuildProject(vcProject); Instances[vcProject.ProjectFile] = project; if (QtOptionsPage.ProjectTracking) { InitQueue.Enqueue(project); _ = Task.Run(InitDispatcherLoopAsync); } var configs = (vcProject.Configurations as IVCCollection) ?.OfType() ?? Enumerable.Empty(); foreach (var config in configs) { if (config.Rules.Item("QtRule10_Settings") is not IVCRulePropertyStorage props) { continue; } var qtInstall = props.GetEvaluatedPropertyValue("QtInstall"); if (!QtVersionManager.VersionExists(qtInstall)) ShowUpdateQtInstallationMessage(project); } return project; } if (MsBuildProjectFormat.GetVersion(vcProject) >= MsBuildProjectFormat.Version.V1) ShowUpdateFormatMessage(); return null; // ignore old or unknown projects } } public static IReadOnlyCollection GetProjects() { return new List(Instances.Values).AsReadOnly(); } public static void Remove(string projectPath) { lock (StaticCriticalSection) Instances.TryRemove(projectPath, out _); } public static void Reset() { lock (StaticCriticalSection) { Instances.Clear(); InitQueue.Clear(); } CloseUpdateFormatMessage(); CloseProjectFormatUpdated(); } private MsBuildProject(VCProject vcProject) { ThreadHelper.ThrowIfNotOnUIThread(); VcProject = vcProject; VcProjectPath = vcProject.ProjectFile; VcProjectDirectory = vcProject.ProjectDirectory; Initialized = new EventWaitHandle(false, EventResetMode.ManualReset); } public VCProject VcProject { get; } public string VcProjectPath { get; } public string VcProjectDirectory { get; } public string SolutionPath { get; set; } = ""; public bool IsTracked => QtOptionsPage.ProjectTracking && Instances.ContainsKey(VcProjectPath); public string QtVersion { get { ThreadHelper.ThrowIfNotOnUIThread(); return GetPropertyValue("QtInstall"); } } public MsBuildProjectFormat.Version FormatVersion { get { return MsBuildProjectFormat.GetVersion(VcProject); } } public string InstallPath { get { ThreadHelper.ThrowIfNotOnUIThread(); return QtVersionManager.GetInstallPath(this); } } public VersionInformation VersionInfo { get { ThreadHelper.ThrowIfNotOnUIThread(); return VersionInformation.GetOrAddByName(QtVersion); } } public string GetPropertyValue(string propertyName) { ThreadHelper.ThrowIfNotOnUIThread(); return VcProject.ActiveConfiguration is { } activeConfiguration ? activeConfiguration.GetEvaluatedPropertyValue(propertyName) : null; } /// /// Returns the files specified by the file name from a given project as list of VCFile /// objects. /// /// file name (relative path) /// public IEnumerable GetFilesFromProject(string fileName) { var fi = new FileInfo(HelperFunctions.NormalizeRelativeFilePath(fileName)); foreach (VCFile f in (IVCCollection)VcProject.Files) { if (string.Equals(f.Name, fi.Name, IgnoreCase)) yield return f; } } public void MarkAsQtPlugin() { if (VcProject.Configurations is not IVCCollection configurations) return; foreach (VCConfiguration config in configurations) { if (config.Rules.Item("QtRule10_Settings") is IVCRulePropertyStorage rule) rule.SetPropertyValue("QtPlugin", "true"); } } public void EnableActiveQtBuildStep(string version, string defFile = null) { ThreadHelper.ThrowIfNotOnUIThread(); foreach (VCConfiguration config in (IVCCollection)VcProject.Configurations) { if (config.Rules.Item("QtRule80_IDC") is IVCRulePropertyStorage rule) { rule.SetPropertyValue("QtIDC", "true"); rule.SetPropertyValue("QtIDCVersion", version); } var linker = (VCLinkerTool)((IVCCollection)config.Tools).Item("VCLinkerTool"); var librarian = (VCLibrarianTool)((IVCCollection)config.Tools).Item("VCLibrarianTool"); if (linker != null) { linker.Version = version; linker.ModuleDefinitionFile = defFile ?? VcProject.Name + ".def"; } else { librarian.ModuleDefinitionFile = defFile ?? VcProject.Name + ".def"; } } } public bool UsesPrecompiledHeaders() { if (VcProject.Configurations is not IVCCollection configurations) return false; const pchOption pchNone = pchOption.pchNone; return configurations.Cast() .Select(CompilerToolWrapper.Create) .All(compiler => (compiler?.GetUsePrecompiledHeader() ?? pchNone) != pchNone); } public string GetPrecompiledHeaderThrough() { if (VcProject.Configurations is not IVCCollection configurations) return null; return configurations.Cast() .Select(CompilerToolWrapper.Create) .Select(compiler => compiler?.GetPrecompiledHeaderThrough() ?? "") .Where(header => !string.IsNullOrEmpty(header)) .Select(header => header.ToLower()) .FirstOrDefault(); } public static void SetPCHOption(VCFile vcFile, pchOption option) { if (vcFile.FileConfigurations is not IVCCollection fileConfigurations) return; foreach (VCFileConfiguration config in fileConfigurations) CompilerToolWrapper.Create(config)?.SetUsePrecompiledHeader(option); } public void RemoveGeneratedFiles(string fileName) { var fi = new FileInfo(fileName); var lastIndex = fileName.LastIndexOf(fi.Extension, StringComparison.Ordinal); var baseName = fi.Name.Remove(lastIndex, fi.Extension.Length); string delName = null; if (HelperFunctions.IsHeaderFile(fileName)) delName = "moc_" + baseName + ".cpp"; else if (HelperFunctions.IsSourceFile(fileName) && !fileName.StartsWith("moc_", IgnoreCase)) delName = baseName + ".moc"; else if (HelperFunctions.IsUicFile(fileName)) delName = "ui_" + baseName + ".h"; else if (HelperFunctions.IsQrcFile(fileName)) delName = "qrc_" + baseName + ".cpp"; if (delName != null) { foreach (var vcFile in GetFilesFromProject(delName)) vcFile.DeleteAndRemoveFromFilter(FakeFilter.GeneratedFiles()); } } public bool SelectInSolutionExplorer() { ThreadHelper.ThrowIfNotOnUIThread(); if (VsServiceProvider.Instance is not IServiceProvider provider) return false; var explorer = VsShellUtilities.GetUIHierarchyWindow(provider, VSConstants.StandardToolWindows.SolutionExplorer); var hierarchy = VsShellUtilities.GetHierarchy(provider, Guid.Parse(VcProject.ProjectGUID)) as IVsUIHierarchy; if (explorer == null || hierarchy == null) return false; explorer.ExpandItem(hierarchy, VSConstants.VSITEMID_ROOT, EXPANDFLAGS.EXPF_SelectItem); var ret = explorer.GetItemState(hierarchy, VSConstants.VSITEMID_ROOT, (uint)__VSHIERARCHYITEMSTATE.HIS_Selected, out var state); return !ErrorHandler.Failed(ret) && state == (uint)__VSHIERARCHYITEMSTATE.HIS_Selected; } } }