diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/NuGet.PackageManagement.VisualStudio.csproj b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/NuGet.PackageManagement.VisualStudio.csproj index b8214136ea8..c0ad3768831 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/NuGet.PackageManagement.VisualStudio.csproj +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/NuGet.PackageManagement.VisualStudio.csproj @@ -73,6 +73,7 @@ + diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/ProjectServices/VCProjectSystemServices.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/ProjectServices/VCProjectSystemServices.cs new file mode 100644 index 00000000000..fe201d58332 --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/ProjectServices/VCProjectSystemServices.cs @@ -0,0 +1,398 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft; +using Microsoft.VisualStudio.ComponentModelHost; +using NuGet.Commands; +using NuGet.Common; +using NuGet.Frameworks; +using NuGet.LibraryModel; +using NuGet.ProjectManagement; +using NuGet.ProjectModel; +using NuGet.Versioning; +using NuGet.VisualStudio; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.VCProjectEngine; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; +using MicrosoftBuildEvaluationProject = Microsoft.Build.Evaluation.Project; + +namespace NuGet.PackageManagement.VisualStudio +{ + /// + /// Contains the information specific to a Visual C++ project. + /// + internal class VCProjectSystemServices + : GlobalProjectServiceProvider + , INuGetProjectServices + , IProjectSystemCapabilities + , IProjectSystemReferencesReader + , IProjectSystemReferencesService + { + private static readonly Array _referenceMetadata; + + private readonly IVsProjectAdapter _vsProjectAdapter; + private readonly IVsProjectThreadingService _threadingService; + private readonly Lazy _asVCProject; + + private VCProject AsVCProject => _asVCProject.Value; + + public bool SupportsPackageReferences => true; + + #region INuGetProjectServices + + public IProjectBuildProperties BuildProperties => _vsProjectAdapter.BuildProperties; + + public IProjectSystemCapabilities Capabilities => this; + + public IProjectSystemReferencesReader ReferencesReader => this; + + public IProjectSystemReferencesService References => this; + + public IProjectSystemService ProjectSystem => throw new NotSupportedException(); + + public IProjectScriptHostService ScriptService { get; } + + #endregion INuGetProjectServices + + static VCProjectSystemServices() + { + _referenceMetadata = Array.CreateInstance(typeof(string), 5); + _referenceMetadata.SetValue(ProjectItemProperties.IncludeAssets, 0); + _referenceMetadata.SetValue(ProjectItemProperties.ExcludeAssets, 1); + _referenceMetadata.SetValue(ProjectItemProperties.PrivateAssets, 2); + _referenceMetadata.SetValue(ProjectItemProperties.NoWarn, 3); + _referenceMetadata.SetValue(ProjectItemProperties.GeneratePathProperty, 4); + } + + public VCProjectSystemServices( + IVsProjectAdapter vsProjectAdapter, + IComponentModel componentModel) + : base(componentModel) + { + Assumes.Present(vsProjectAdapter); + + _vsProjectAdapter = vsProjectAdapter; + + _threadingService = GetGlobalService(); + Assumes.Present(_threadingService); + + _asVCProject = new Lazy(() => vsProjectAdapter.Project.Object as VCProject); + + ScriptService = new VsProjectScriptHostService(vsProjectAdapter, this); + } + + public async Task> GetPackageReferencesAsync( + NuGetFramework targetFramework, CancellationToken _) + { + Assumes.Present(targetFramework); + + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + + + IEnumerable references = null; + + await ProjectHelper.DoWorkInReadLockAsync( + _vsProjectAdapter.Project, + _vsProjectAdapter.VsHierarchy, + buildProject => references = GetPackageReferencesAsync(buildProject, targetFramework)); + + + if(references != null) + return references.ToList(); + else + return new LibraryDependency[] { }; + } + + private IEnumerable GetPackageReferencesAsync( + MicrosoftBuildEvaluationProject msBuildEvaluationproject, + NuGetFramework targetFramework) + { + var packageReferences = msBuildEvaluationproject.GetItems("PackageReference"); + + if (packageReferences != null && packageReferences.Count != 0) + { + var references = packageReferences.Select(installedPackage => + { + List metadataElements = new List(); + List metadataValues = new List(); + + + foreach (var item in installedPackage.Metadata) + { + if (item.Name.Equals("Version", StringComparison.OrdinalIgnoreCase) == false) + { + metadataElements.Add(item.Name); + metadataValues.Add(item.EvaluatedValue); + } + } + + + return new PackageReference( + name: installedPackage.EvaluatedInclude, + version: installedPackage.GetMetadataValue("Version"), + metadataElements: metadataElements.ToArray(), + metadataValues: metadataValues.ToArray(), + targetNuGetFramework: targetFramework); + + }) + .Where(p => p != null) + .Select(ToPackageLibraryDependency); + + return references; + } + + return null; + } + + //Have PackageReference? + public static bool HasPackageReference(IVsProjectAdapter _vsProjectAdapter) + { + Assumes.Present(_vsProjectAdapter); + + + + return NuGetUIThreadHelper.JoinableTaskFactory.Run(async delegate + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + bool bHasPackageReference = false; + + await ProjectHelper.DoWorkInReadLockAsync( + _vsProjectAdapter.Project, + _vsProjectAdapter.VsHierarchy, + buildProject => + { + //We should only care about the existence of a PackageReference, and not whether the Condition evaluates to false. + var packageReferences = buildProject.Get​Items​Ignoring​Condition("PackageReference"); + bHasPackageReference = packageReferences != null && packageReferences.Count != 0; + }); + + return bHasPackageReference; + }); + } + + public async Task> GetProjectReferencesAsync( + Common.ILogger _, CancellationToken __) + { + await _threadingService.JoinableTaskFactory.SwitchToMainThreadAsync(); + + //C++ Project not support Reference + return new ProjectRestoreReference[] { }; + } + + private static LibraryDependency ToPackageLibraryDependency(PackageReference reference) + { + var dependency = new LibraryDependency + { + AutoReferenced = MSBuildStringUtility.IsTrue(GetReferenceMetadataValue(reference, ProjectItemProperties.IsImplicitlyDefined)), + GeneratePathProperty = MSBuildStringUtility.IsTrue(GetReferenceMetadataValue(reference, ProjectItemProperties.GeneratePathProperty)), + LibraryRange = new LibraryRange( + name: reference.Name, + versionRange: VersionRange.Parse(reference.Version), + typeConstraint: LibraryDependencyTarget.Package) + }; + + MSBuildRestoreUtility.ApplyIncludeFlags( + dependency, + GetReferenceMetadataValue(reference, ProjectItemProperties.IncludeAssets), + GetReferenceMetadataValue(reference, ProjectItemProperties.ExcludeAssets), + GetReferenceMetadataValue(reference, ProjectItemProperties.PrivateAssets)); + + + // Add warning suppressions + foreach (var code in MSBuildStringUtility.GetNuGetLogCodes(GetReferenceMetadataValue(reference, ProjectItemProperties.NoWarn))) + { + dependency.NoWarn.Add(code); + } + + return dependency; + } + + private static string GetReferenceMetadataValue(PackageReference reference, string metadataElement) + { + Assumes.Present(reference); + Assumes.NotNullOrEmpty(metadataElement); + + if (reference.MetadataElements == null || reference.MetadataValues == null) + { + return string.Empty; // no metadata for package + } + + var index = Array.IndexOf(reference.MetadataElements, metadataElement); + if (index >= 0) + { + return reference.MetadataValues.GetValue(index) as string; + } + + return string.Empty; + } + + public async Task AddOrUpdatePackageReferenceAsync(LibraryDependency packageReference, CancellationToken _) + { + Assumes.Present(packageReference); + + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var includeFlags = packageReference.IncludeType; + var privateAssetsFlag = packageReference.SuppressParent; + var metadataElements = new List(); + var metadataValues = new List(); + if (includeFlags != LibraryIncludeFlags.All) + { + metadataElements.Add(ProjectItemProperties.IncludeAssets); + metadataValues.Add(LibraryIncludeFlagUtils.GetFlagString(includeFlags).Replace(',', ';')); + } + + if (privateAssetsFlag != LibraryIncludeFlagUtils.DefaultSuppressParent) + { + metadataElements.Add(ProjectItemProperties.PrivateAssets); + metadataValues.Add(LibraryIncludeFlagUtils.GetFlagString(privateAssetsFlag).Replace(',', ';')); + } + + await ProjectHelper.DoWorkInWriterLockAsync( + _vsProjectAdapter.Project, + _vsProjectAdapter.VsHierarchy, + buildProject => AddOrUpdatePackageReference( + buildProject, + packageReference.Name, + packageReference.LibraryRange.VersionRange, + metadataElements.ToArray(), + metadataValues.ToArray())); + } + + private void AddOrUpdatePackageReference(MicrosoftBuildEvaluationProject msBuildEvaluationproject, string packageName, VersionRange packageVersion, string[] metadataElements, string[] metadataValues) + { + // Note that API behavior is: + // - specify a metadata element name with a value => add/replace that metadata item on the package reference + // - specify a metadata element name with no value => remove that metadata item from the project reference + // - don't specify a particular metadata name => if it exists on the package reference, don't change it (e.g. for user defined metadata) + + var packageReferences = msBuildEvaluationproject.GetItems("PackageReference"); + var metadataElementCount = metadataElements.Length < metadataValues.Length ? metadataElements.Length : metadataValues.Length; + + var szPackageVersion = packageVersion.OriginalString ?? packageVersion.ToShortString(); + + foreach (ProjectItem packageReferenceProjectItem in packageReferences) + { + if (packageReferenceProjectItem.EvaluatedInclude.Equals(packageName, StringComparison.OrdinalIgnoreCase)) + { + //Update PackageReference + packageReferenceProjectItem.SetMetadataValue("Version", szPackageVersion); + + for (int i = 0; i != metadataElementCount; ++i) + { + if (metadataValues[i] == null || metadataValues[i].Length == 0) + packageReferenceProjectItem.RemoveMetadata(metadataElements[i]); + else + packageReferenceProjectItem.SetMetadataValue(metadataElements[i], metadataValues[i]); + } + + msBuildEvaluationproject.ReevaluateIfNecessary(); + return; + } + } + + ProjectItemElement itemElement = null; + + //add new + if (packageReferences.Count != 0) + { + itemElement = msBuildEvaluationproject.Xml.CreateItemElement("PackageReference", packageName); + + var where = packageReferences.Last().Xml; + + where.Parent.InsertAfterChild(itemElement, where); + } + else + { + var itemGroup = msBuildEvaluationproject.Xml.AddItemGroup(); + + itemElement = itemGroup.AddItem("PackageReference", packageName); + } + + + //Set PackageReference + itemElement.AddMetadata("Version", szPackageVersion); + + + for (int i = 0; i != metadataElementCount; ++i) + { + if (metadataValues[i] != null && metadataValues[i].Length != 0) + itemElement.AddMetadata(metadataElements[i], metadataValues[i]); + } + + msBuildEvaluationproject.ReevaluateIfNecessary(); + + return; + } + + public async Task RemovePackageReferenceAsync(string packageName) + { + Assumes.NotNullOrEmpty(packageName); + + + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + await ProjectHelper.DoWorkInWriterLockAsync( + _vsProjectAdapter.Project, + _vsProjectAdapter.VsHierarchy, + buildProject => RemovePackageReferenceAsync(buildProject, packageName)); + } + + private void RemovePackageReferenceAsync(MicrosoftBuildEvaluationProject msBuildEvaluationproject, string packageName) + { + var packageReferences = msBuildEvaluationproject.GetItems("PackageReference"); + + foreach (ProjectItem packageReferenceProjectItem in packageReferences) + { + if (packageReferenceProjectItem.EvaluatedInclude.Equals(packageName, StringComparison.OrdinalIgnoreCase)) + { + var packageReferenceParent = packageReferenceProjectItem.Xml.Parent; + + packageReferenceParent.RemoveChild(packageReferenceProjectItem.Xml); + + + if (packageReferenceParent.Count == 0) + { + packageReferenceParent.Parent.RemoveChild(packageReferenceParent); + } + + + msBuildEvaluationproject.ReevaluateIfNecessary(); + + break; + } + } + } + + private class PackageReference + { + public PackageReference( + string name, + string version, + Array metadataElements, + Array metadataValues, + NuGetFramework targetNuGetFramework) + { + Name = name; + Version = version; + MetadataElements = metadataElements; + MetadataValues = metadataValues; + TargetNuGetFramework = targetNuGetFramework; + } + + public string Name { get; } + public string Version { get; } + public Array MetadataElements { get; } + public Array MetadataValues { get; } + public NuGetFramework TargetNuGetFramework { get; } + } + } +} diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProjectProvider.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProjectProvider.cs index 359ee3a738c..ce829d7e209 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProjectProvider.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProjectProvider.cs @@ -116,6 +116,16 @@ private async Task TryCreateProjectServicesAsync( () => CreateCoreProjectSystemServices(vsProjectAdapter, componentModel), componentModel); } + else if (vsProjectAdapter.Project.Object as Microsoft.VisualStudio.VCProjectEngine.VCProject != null) + { + //VC Project + if (forceCreate + || PackageReference.Equals(restoreProjectStyle, StringComparison.OrdinalIgnoreCase) + || VCProjectSystemServices.HasPackageReference(vsProjectAdapter)) + { + return new VCProjectSystemServices(vsProjectAdapter, componentModel); + } + } else { var asVSProject4 = vsProjectAdapter.Project.Object as VSProject4; diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsMSBuildProjectSystemServices.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsMSBuildProjectSystemServices.cs index a2ffd2820bd..9edc595e30e 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsMSBuildProjectSystemServices.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsMSBuildProjectSystemServices.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -44,7 +44,8 @@ public bool SupportsPackageReferences return _threadingService.ExecuteSynchronously(async () => { await _threadingService.JoinableTaskFactory.SwitchToMainThreadAsync(); - return _vsProjectAdapter.Project.Object is VSLangProj150.VSProject4; + return _vsProjectAdapter.Project.Object is VSLangProj150.VSProject4 + || _vsProjectAdapter.Project.Object is Microsoft.VisualStudio.VCProjectEngine.VCProject; }); } } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsProjectAdapter.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsProjectAdapter.cs index 98906110a44..1b54dba6eb5 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsProjectAdapter.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/VsProjectAdapter.cs @@ -393,6 +393,10 @@ private async Task GetTargetFrameworkStringAsync() await _threadingService.JoinableTaskFactory.SwitchToMainThreadAsync(); var projectPath = FullName; + var targetFrameworks = await BuildProperties.GetPropertyValueAsync( + ProjectBuildProperties.TargetFrameworks); + var targetFramework = await BuildProperties.GetPropertyValueAsync( + ProjectBuildProperties.TargetFramework); var platformIdentifier = await BuildProperties.GetPropertyValueAsync( ProjectBuildProperties.TargetPlatformIdentifier); var platformVersion = await BuildProperties.GetPropertyValueAsync( @@ -406,8 +410,8 @@ private async Task GetTargetFrameworkStringAsync() // this check. The values can be passed as null here. var frameworkStrings = MSBuildProjectFrameworkUtility.GetProjectFrameworkStrings( projectFilePath: projectPath, - targetFrameworks: null, - targetFramework: null, + targetFrameworks: targetFrameworks, + targetFramework: targetFramework, targetFrameworkMoniker: targetFrameworkMoniker, targetPlatformIdentifier: platformIdentifier, targetPlatformVersion: platformVersion, diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Utility/NuGetProjectUpgradeUtility.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Utility/NuGetProjectUpgradeUtility.cs index eb2c6bebc06..0baaadc2a5c 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Utility/NuGetProjectUpgradeUtility.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Utility/NuGetProjectUpgradeUtility.cs @@ -23,13 +23,13 @@ public class NuGetProjectUpgradeUtility { VsProjectTypes.CsharpProjectTypeGuid, VsProjectTypes.VbProjectTypeGuid, - VsProjectTypes.FsharpProjectTypeGuid + VsProjectTypes.FsharpProjectTypeGuid, + VsProjectTypes.CppProjectTypeGuid, }; private static readonly HashSet UnupgradeableProjectTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { - VsProjectTypes.CppProjectTypeGuid, VsProjectTypes.WebApplicationProjectTypeGuid, VsProjectTypes.WebSiteProjectTypeGuid }; diff --git a/src/NuGet.Clients/NuGet.VisualStudio.Common/ProjectHelper.cs b/src/NuGet.Clients/NuGet.VisualStudio.Common/ProjectHelper.cs index 5ebe684948f..e7863b67195 100644 --- a/src/NuGet.Clients/NuGet.VisualStudio.Common/ProjectHelper.cs +++ b/src/NuGet.Clients/NuGet.VisualStudio.Common/ProjectHelper.cs @@ -46,6 +46,35 @@ public static async Task DoWorkInWriterLockAsync(Project project, IVsHierarchy h } } + public static async Task DoWorkInReadLockAsync(Project project, IVsHierarchy hierarchy, Action action) + { + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var vsProject = (IVsProject)hierarchy; + UnconfiguredProject unconfiguredProject = GetUnconfiguredProject(vsProject); + if (unconfiguredProject != null) + { + var service = unconfiguredProject.ProjectService.Services.ProjectLockService; + if (service != null) + { + using (var x = await service.ReadLockAsync()) + { + ConfiguredProject configuredProject = await unconfiguredProject.GetSuggestedConfiguredProjectAsync(); + MsBuildProject buildProject = await x.GetProjectAsync(configuredProject); + + if (buildProject != null) + { + action(buildProject); + } + + await x.ReleaseAsync(); + } + + await unconfiguredProject.ProjectService.Services.ThreadingPolicy.SwitchToUIThread(); + } + } + } + private static UnconfiguredProject GetUnconfiguredProject(IVsProject project) { ThreadHelper.ThrowIfNotOnUIThread(); diff --git a/src/NuGet.Core/NuGet.ProjectModel/LockFile/BuildAction.cs b/src/NuGet.Core/NuGet.ProjectModel/LockFile/BuildAction.cs index c50d3e2ebf3..8783dca27c5 100644 --- a/src/NuGet.Core/NuGet.ProjectModel/LockFile/BuildAction.cs +++ b/src/NuGet.Core/NuGet.ProjectModel/LockFile/BuildAction.cs @@ -25,6 +25,10 @@ public struct BuildAction : IEquatable public static BuildAction AndroidAsset = Define(nameof(AndroidAsset)); public static BuildAction AndroidResource = Define(nameof(AndroidResource)); public static BuildAction BundleResource = Define(nameof(BundleResource)); + public static BuildAction ClCompile = Define(nameof(ClCompile)); + public static BuildAction ResourceCompile = Define(nameof(ResourceCompile)); + public static BuildAction MASM = Define(nameof(MASM)); + public static BuildAction Midl = Define(nameof(Midl)); public string Value { get; } @@ -87,4 +91,4 @@ private static BuildAction Define(string name) return buildAction; } } -} \ No newline at end of file +}