diff --git a/FineCodeCoverage/FineCodeCoverage.csproj b/FineCodeCoverage/FineCodeCoverage.csproj index 40b67f14..49d4574d 100644 --- a/FineCodeCoverage/FineCodeCoverage.csproj +++ b/FineCodeCoverage/FineCodeCoverage.csproj @@ -171,6 +171,9 @@ 16.9.20 + + 15.8.243 + compile; build; native; contentfiles; analyzers; buildtransitive diff --git a/FineCodeCoverage2022/FineCodeCoverage2022.csproj b/FineCodeCoverage2022/FineCodeCoverage2022.csproj index 843ca610..a74d9eb6 100644 --- a/FineCodeCoverage2022/FineCodeCoverage2022.csproj +++ b/FineCodeCoverage2022/FineCodeCoverage2022.csproj @@ -170,6 +170,9 @@ 17.10.37 + + 17.9.380 + compile; build; native; contentfiles; analyzers; buildtransitive diff --git a/FineCodeCoverageTests/CoverageProject_Tests.cs b/FineCodeCoverageTests/CoverageProject_Tests.cs new file mode 100644 index 00000000..7cc1016b --- /dev/null +++ b/FineCodeCoverageTests/CoverageProject_Tests.cs @@ -0,0 +1,152 @@ +using FineCodeCoverage.Engine.Model; +using NUnit.Framework; +using System.IO; + +namespace FineCodeCoverageTests +{ + public class CoverageProject_Tests + { + private string tempProjectFilePath; + private CoverageProject coverageProject; + + [SetUp] + public void SetUp() + { + coverageProject = new CoverageProject(null, null, null, null, null, false); + tempProjectFilePath = Path.Combine(Path.GetTempPath(), "testproject.csproj"); + coverageProject.ProjectFile = tempProjectFilePath; + } + + [Test] + public void Should_Be_An_Sdk_Project_When_Project_Element_Has_Sdk_Attribute() + { + Test(@"", true); + } + + [Test] + public void Should_Be_An_Sdk_Project_When_Project_Element_Has_Child_Sdk_Element() + { + Test(@" + + +", true); + } + + [Test] + public void Should_be_An_Sdk_Project_When_Project_Element_Has_Child_Import_With_Sdk_Attribute() + { + Test(@" + + Value + + + +", true); + } + + [Test] + public void Should_Not_Be_An_Sdk_Project_When_Is_Not() + { + Test(@" + + + + + Debug + AnyCPU + {F4B73E7F-F91D-4C95-A3CE-B8DA3A94BB90} + Library + Properties + NetFrameworkUnitTest + NetFrameworkUnitTest + v4.7.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.CodeCoverage.17.12.0\lib\net462\Microsoft.VisualStudio.CodeCoverage.Shim.dll + + + ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + {9ef61906-87e7-42ac-b977-058fc55f74d8} + DemoOpenCover + + + {fa82235f-9590-410b-9840-daa224abb17e} + Exclude + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + +", false); + } + + private void Test(string project, bool expectedIsSdkStyle) + { + var xmlDeclaration = "\r\n"; + File.WriteAllText(tempProjectFilePath, $"{xmlDeclaration}{project}"); + Assert.That(coverageProject.IsDotNetSdkStyle(), Is.EqualTo(expectedIsSdkStyle)); + } + + [TearDown] + public void Delete_ProjectFile() + { + File.Delete(tempProjectFilePath); + } + } + +} diff --git a/FineCodeCoverageTests/FineCodeCoverageTests.csproj b/FineCodeCoverageTests/FineCodeCoverageTests.csproj index 53e88952..b4c2efbf 100644 --- a/FineCodeCoverageTests/FineCodeCoverageTests.csproj +++ b/FineCodeCoverageTests/FineCodeCoverageTests.csproj @@ -66,6 +66,7 @@ + diff --git a/README.md b/README.md index 4033a67b..263bd889 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,15 @@ or download from [releases](https://github.com/FortuneN/FineCodeCoverage/release ## Prerequisites -For .Net - that the test adapters are nuget packages. For instance, the NUnit Test Adapter extension is not sufficient. +For .Net + +FCC supports the new [Microsoft.Testing.Platform]( https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-platform-intro). +FCC does not support the associated testing platform server mode for when using microsoft as a coverage provider. +You can disable this feature - "Options / Environment / Preview Features / Use testing platform server mode" +but it is not necessary as FCC adds a global msbuild property, DisableTestingPlatformServerCapability true, that removes +the project capability. ([see Microsoft.Testing.Platform.MSBuild.targets](https://github.com/microsoft/testfx/blob/d141931b99fad0617d8435ce321fca0c45c9eb94/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets#L10)). + +When not using Microsoft.TestingPlatform you have added test adapters through nuget packages. For instance, the NUnit Test Adapter extension is not sufficient. --- diff --git a/SharedProject/Core/Model/CoverageProject.cs b/SharedProject/Core/Model/CoverageProject.cs index 7e648e9d..d867bf58 100644 --- a/SharedProject/Core/Model/CoverageProject.cs +++ b/SharedProject/Core/Model/CoverageProject.cs @@ -32,6 +32,7 @@ internal class CoverageProject : ICoverageProject private readonly string fccFolderName = "fine-code-coverage"; private readonly string buildOutputFolderName = "build-output"; private string buildOutputPath; + private bool? isDotNetSdkStyle; private string BuildOutputPath { get @@ -78,84 +79,50 @@ public CoverageProject( public string FCCOutputFolder => Path.Combine(ProjectOutputFolder, fccFolderName); public bool IsDotNetSdkStyle() { - return ProjectFileXElement + if (isDotNetSdkStyle.HasValue) + { + return isDotNetSdkStyle.Value; + } + + isDotNetSdkStyle = ProjectFileXElement .DescendantsAndSelf() .Where(x => { //https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2019 + return IsRootProjectElementWithSdkAttribute(x) || + IsRootProjectElementSdkElementChild(x) || + IsRootImportElementWithSdkAttribute(x); + }) + .Any(); - /* - - ... - - - ... - - */ - if - ( - x?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true && - x?.Parent == null - ) - { - var sdkAttr = x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true); - - if (sdkAttr?.Value?.Trim()?.StartsWith("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase) == true) - { - return true; - } - } + return isDotNetSdkStyle.Value; - /* - - - ... - - */ - if - ( - x?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true && - x?.Parent?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true && - x?.Parent?.Parent == null - ) - { - var nameAttr = x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Name", StringComparison.OrdinalIgnoreCase) == true); + bool HasSdkAttribute(XElement x) + { + return x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true) != null; + } - if (nameAttr?.Value?.Trim()?.StartsWith("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase) == true) - { - return true; - } - } + bool IsRootProjectElementWithSdkAttribute(XElement x) + { + return x?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true && + x?.Parent == null && HasSdkAttribute(x); + } - /* - - - Value - - - ... - - - */ - if - ( - x?.Name?.LocalName?.Equals("Import", StringComparison.OrdinalIgnoreCase) == true && + bool IsRootProjectElementSdkElementChild(XElement x) + { + return x?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true && x?.Parent?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true && - x?.Parent?.Parent == null - ) - { - var sdkAttr = x?.Attributes()?.FirstOrDefault(attr => attr?.Name?.LocalName?.Equals("Sdk", StringComparison.OrdinalIgnoreCase) == true); - - if (sdkAttr?.Value?.Trim()?.StartsWith("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase) == true) - { - return true; - } - } + x?.Parent?.Parent == null; + } - return false; - }) - .Any(); + bool IsRootImportElementWithSdkAttribute(XElement x) + { + return x?.Name?.LocalName?.Equals("Import", StringComparison.OrdinalIgnoreCase) == true && + x?.Parent?.Name?.LocalName?.Equals("Project", StringComparison.OrdinalIgnoreCase) == true && + x?.Parent?.Parent == null && HasSdkAttribute(x); + } } + public string TestDllFile { get; set; } public string ProjectOutputFolder => Path.GetDirectoryName(TestDllFile); public string FailureDescription { get; set; } diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs index e5f03004..d7cf949d 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/ProjectSaver.cs @@ -9,7 +9,7 @@ namespace FineCodeCoverage.Core.MsTestPlatform.CodeCoverage { interface IProjectSaver { - void SaveProject(IVsHierarchy projectHierarchy); + System.Threading.Tasks.Task SaveProjectAsync(IVsHierarchy projectHierarchy); } [Export(typeof(IProjectSaver))] @@ -26,9 +26,9 @@ IServiceProvider serviceProvider this.serviceProvider = serviceProvider; } - public void SaveProject(IVsHierarchy projectHierarchy) + public async System.Threading.Tasks.Task SaveProjectAsync(IVsHierarchy projectHierarchy) { - ThreadHelper.ThrowIfNotOnUIThread(); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var _solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)); Assumes.Present(_solution); int hr = _solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_SaveIfDirty, projectHierarchy, 0); diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs index fa603582..7a957f52 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/VsRunSettingsWriter.cs @@ -6,6 +6,7 @@ using System.ComponentModel.Composition; using System.Threading.Tasks; using FineCodeCoverage.Core.MsTestPlatform.CodeCoverage; +using FineCodeCoverage.Core.Utilities; namespace FineCodeCoverage.Engine.MsTestPlatform.CodeCoverage { @@ -15,16 +16,19 @@ internal class VsRunSettingsWriter : IVsRunSettingsWriter private const string projectRunSettingsFilePathElementName = "RunSettingsFilePath"; private readonly IServiceProvider serviceProvider; private readonly IProjectSaver projectSaver; + private readonly IProjectFilePropertyWriter projectFilePropertyWriter; [ImportingConstructor] public VsRunSettingsWriter( [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, - IProjectSaver projectSaver + IProjectSaver projectSaver, + IProjectFilePropertyWriter projectFilePropertyWriter ) { this.serviceProvider = serviceProvider; this.projectSaver = projectSaver; + this.projectFilePropertyWriter = projectFilePropertyWriter; } public async Task WriteRunSettingsFilePathAsync(Guid projectGuid, string projectRunSettingsFilePath) @@ -35,31 +39,23 @@ public async Task WriteRunSettingsFilePathAsync(Guid projectGuid, string p Assumes.Present(vsSolution); if (vsSolution.GetProjectOfGuid(ref projectGuid, out var vsHierarchy) == VSConstants.S_OK) { - if (vsHierarchy is IVsBuildPropertyStorage vsBuildPropertyStorage) - { - success = vsBuildPropertyStorage.SetPropertyValue(projectRunSettingsFilePathElementName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE, projectRunSettingsFilePath) == VSConstants.S_OK; - } + success = await projectFilePropertyWriter.WritePropertyAsync(vsHierarchy, projectRunSettingsFilePathElementName, projectRunSettingsFilePath); } return success; } public async Task RemoveRunSettingsFilePathAsync(Guid projectGuid) { - var ok = false; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var vsSolution = serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution; Assumes.Present(vsSolution); if (vsSolution.GetProjectOfGuid(ref projectGuid, out var vsHierarchy) == VSConstants.S_OK) { - if (vsHierarchy is IVsBuildPropertyStorage vsBuildPropertyStorage) + ok = await projectFilePropertyWriter.RemovePropertyAsync(vsHierarchy, projectRunSettingsFilePathElementName); + if (ok) { - ok = vsBuildPropertyStorage.RemoveProperty(projectRunSettingsFilePathElementName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE) == VSConstants.S_OK; - - if (ok) - { - this.projectSaver.SaveProject(vsHierarchy); - } + await this.projectSaver.SaveProjectAsync(vsHierarchy); } } return ok; diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs new file mode 100644 index 00000000..32aedfdf --- /dev/null +++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/DisableTestingPlatformServerCapabilityGlobalPropertiesProvider.cs @@ -0,0 +1,112 @@ +using FineCodeCoverage.Engine.Model; +using FineCodeCoverage.Engine.MsTestPlatform.TestingPlatform; +using FineCodeCoverage.Options; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Build; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Threading; + +namespace FineCodeCoverage.Core.MsTestPlatform.TestingPlatform +{ + // https://github.com/dotnet/project-system + // https://github.com/microsoft/VSProjectSystem/blob/master/doc/extensibility/IProjectGlobalPropertiesProvider.md + [Export(typeof(IProjectGlobalPropertiesProvider))] + // https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform/buildMultiTargeting/Microsoft.Testing.Platform.props + /* + https://github.com/microsoft/VSProjectSystem/blob/master/doc/overview/about_project_capabilities.md + Classes exported via MEF can declare the project capabilities under which they apply. + + See https://learn.microsoft.com/en-gb/dotnet/api/microsoft.visualstudio.shell.interop.vsprojectcapabilityexpressionmatcher?view=visualstudiosdk-2022 + For expression syntax + */ + [AppliesTo("TestingPlatformServer.ExitOnProcessExitCapability | TestingPlatformServer.UseListTestsOptionForDiscoveryCapability")] + internal class DisableTestingPlatformServerCapabilityGlobalPropertiesProvider : StaticGlobalPropertiesProviderBase + { + private readonly IUseTestingPlatformProtocolFeatureService useTestingPlatformProtocolFeatureService; + private readonly IAppOptionsProvider appOptionsProvider; + private readonly ICoverageProjectSettingsManager coverageProjectSettingsManager; + private CoverageProject coverageProject; + [ImportingConstructor] + public DisableTestingPlatformServerCapabilityGlobalPropertiesProvider( + IUseTestingPlatformProtocolFeatureService useTestingPlatformProtocolFeatureService, + IProjectService projectService, + UnconfiguredProject unconfiguredProject, + IAppOptionsProvider appOptionsProvider, + ICoverageProjectSettingsManager coverageProjectSettingsManager + ) + : base((IProjectCommonServices)projectService.Services) + { + ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var hostObject = unconfiguredProject.Services.HostObject; + + var vsHierarchy = (IVsHierarchy)hostObject; + if (vsHierarchy != null) + { + var success = vsHierarchy.GetGuidProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, out Guid projectGuid) == VSConstants.S_OK; + + if (success) + { + // todo - ICoverageProjectSettingsManager.GetSettingsAsync parameter + // to change to what it actually needs + coverageProject = new CoverageProject(appOptionsProvider, null, null, null, coverageProjectSettingsManager, false) + { + Id = projectGuid, + ProjectFile = unconfiguredProject.FullPath + }; + } + } + }); + + this.useTestingPlatformProtocolFeatureService = useTestingPlatformProtocolFeatureService; + this.appOptionsProvider = appOptionsProvider; + this.coverageProjectSettingsManager = coverageProjectSettingsManager; + } + + // visual studio options states that a restart is required. If this is true then could cache this value + private async System.Threading.Tasks.Task UsingTestingPlatformProtocolAsync() + { + var useTestingPlatformProtocolFeature = await useTestingPlatformProtocolFeatureService.GetAsync(); + return useTestingPlatformProtocolFeature.HasValue && useTestingPlatformProtocolFeature.Value; + } + + private bool AllProjectsDisabled() + { + var appOptions = appOptionsProvider.Get(); + return !appOptions.Enabled && appOptions.DisabledNoCoverage; + } + + private async System.Threading.Tasks.Task ProjectEnabledAsync() + { + if (coverageProject != null) + { + var projectSettings = await coverageProjectSettingsManager.GetSettingsAsync(coverageProject); + return projectSettings.Enabled; + } + return true; + } + + public override async System.Threading.Tasks.Task> GetGlobalPropertiesAsync(CancellationToken cancellationToken) + { + /* + Note that it only matters for ms code coverage but not going to test for that + Main thing is that FCC does not turn off if user has Enterprise which does support + the new feature and has turned off FCC. + */ + if (await UsingTestingPlatformProtocolAsync() && !AllProjectsDisabled() && await ProjectEnabledAsync()) + { + // https://github.com/microsoft/testfx/blob/main/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets + return Empty.PropertiesMap.Add("DisableTestingPlatformServerCapability", "true"); + } + return Empty.PropertiesMap; + } + + } + +} diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/IUseTestingPlatformProtocolFeatureService.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/IUseTestingPlatformProtocolFeatureService.cs new file mode 100644 index 00000000..a7b9e6bf --- /dev/null +++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/IUseTestingPlatformProtocolFeatureService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace FineCodeCoverage.Engine.MsTestPlatform.TestingPlatform +{ + internal interface IUseTestingPlatformProtocolFeatureService + { + Task GetAsync(); + } + +} diff --git a/SharedProject/Core/MsTestPlatform/TestingPlatform/UseTestingPlatformProtocolFeatureService.cs b/SharedProject/Core/MsTestPlatform/TestingPlatform/UseTestingPlatformProtocolFeatureService.cs new file mode 100644 index 00000000..45f6469f --- /dev/null +++ b/SharedProject/Core/MsTestPlatform/TestingPlatform/UseTestingPlatformProtocolFeatureService.cs @@ -0,0 +1,33 @@ +using FineCodeCoverage.Options; +using System.ComponentModel.Composition; + +namespace FineCodeCoverage.Engine.MsTestPlatform.TestingPlatform +{ + [Export(typeof(IUseTestingPlatformProtocolFeatureService))] + internal class UseTestingPlatformProtocolFeatureService : IUseTestingPlatformProtocolFeatureService + { + private readonly IReadOnlyUserSettingsStoreProvider readableUserSettingsStoreProvider; + + [ImportingConstructor] + public UseTestingPlatformProtocolFeatureService( + IReadOnlyUserSettingsStoreProvider readableUserSettingsStoreProvider + ) + { + this.readableUserSettingsStoreProvider = readableUserSettingsStoreProvider; + } + public async System.Threading.Tasks.Task GetAsync() + { + var store = await readableUserSettingsStoreProvider.ProvideAsync(); + + try + { + var value = store.GetInt32("FeatureFlags\\TestingTools\\UnitTesting\\UseTestingPlatformProtocol", "Value"); + return value == 1; + } + catch + { + return null; + } + } + } +} diff --git a/SharedProject/Core/Utilities/IProjectFilePropertyWriter.cs b/SharedProject/Core/Utilities/IProjectFilePropertyWriter.cs new file mode 100644 index 00000000..2c77d3c7 --- /dev/null +++ b/SharedProject/Core/Utilities/IProjectFilePropertyWriter.cs @@ -0,0 +1,11 @@ +using Microsoft.VisualStudio.Shell.Interop; +using System.Threading.Tasks; + +namespace FineCodeCoverage.Core.Utilities +{ + public interface IProjectFilePropertyWriter + { + Task RemovePropertyAsync(IVsHierarchy pHierProj, string propertyName); + Task WritePropertyAsync(IVsHierarchy projectHierarchy, string propertyName, string value); + } +} diff --git a/SharedProject/Core/Utilities/ProjectFilePropertyWriter.cs b/SharedProject/Core/Utilities/ProjectFilePropertyWriter.cs new file mode 100644 index 00000000..85acb35c --- /dev/null +++ b/SharedProject/Core/Utilities/ProjectFilePropertyWriter.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio; +using System.ComponentModel.Composition; + +namespace FineCodeCoverage.Core.Utilities +{ + [Export(typeof(IProjectFilePropertyWriter))] + public class ProjectFilePropertyWriter : IProjectFilePropertyWriter + { + public async System.Threading.Tasks.Task WritePropertyAsync(IVsHierarchy projectHierarchy, string propertyName, string value) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (projectHierarchy is IVsBuildPropertyStorage vsBuildPropertyStorage) + { + if(vsBuildPropertyStorage.GetPropertyValue(propertyName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE, out var v) == VSConstants.S_OK) + { + if (v == value) + { + return true; + } + return vsBuildPropertyStorage.SetPropertyValue(propertyName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE, value) == VSConstants.S_OK; + } + } + + return false; + } + + public async System.Threading.Tasks.Task RemovePropertyAsync(IVsHierarchy pHierProj, string propertyName) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (pHierProj is IVsBuildPropertyStorage vsBuildPropertyStorage) + { + return vsBuildPropertyStorage.RemoveProperty(propertyName, string.Empty, (uint)_PersistStorageType.PST_PROJECT_FILE) == VSConstants.S_OK; + } + return false; + } + } + +} diff --git a/SharedProject/Options/IReadOnlyUserSettingsStoreProvider.cs b/SharedProject/Options/IReadOnlyUserSettingsStoreProvider.cs new file mode 100644 index 00000000..f6701724 --- /dev/null +++ b/SharedProject/Options/IReadOnlyUserSettingsStoreProvider.cs @@ -0,0 +1,9 @@ +using Microsoft.VisualStudio.Settings; + +namespace FineCodeCoverage.Options +{ + internal interface IReadOnlyUserSettingsStoreProvider + { + System.Threading.Tasks.Task ProvideAsync(); + } +} diff --git a/SharedProject/Options/ReadOnlyUserSettingsStoreProvider.cs b/SharedProject/Options/ReadOnlyUserSettingsStoreProvider.cs new file mode 100644 index 00000000..3f6f4d54 --- /dev/null +++ b/SharedProject/Options/ReadOnlyUserSettingsStoreProvider.cs @@ -0,0 +1,24 @@ +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell.Settings; +using Microsoft.VisualStudio.Shell; +using System.Threading.Tasks; +using System.ComponentModel.Composition; + +namespace FineCodeCoverage.Options +{ + [Export(typeof(IReadOnlyUserSettingsStoreProvider))] + internal class ReadOnlyUserSettingsStoreProvider : IReadOnlyUserSettingsStoreProvider + { + private SettingsStore settingsStore; + public async Task ProvideAsync() + { + if (settingsStore == null) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); + return settingsManager.GetReadOnlySettingsStore(SettingsScope.UserSettings); + } + return settingsStore; + } + } +} diff --git a/SharedProject/SharedProject.projitems b/SharedProject/SharedProject.projitems index 23fbaf57..582ce406 100644 --- a/SharedProject/SharedProject.projitems +++ b/SharedProject/SharedProject.projitems @@ -122,6 +122,9 @@ + + + @@ -159,6 +162,7 @@ + @@ -168,6 +172,7 @@ + @@ -391,9 +396,11 @@ + +