diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs index de2293757b6..74cadd88e8e 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs @@ -818,6 +818,8 @@ public OptionsViewModel Options public string PackagePath => _searchResultPackage?.PackagePath; + public bool IsCentralPackageManagementEnabled { get; set; } + protected void AddBlockedVersions(List blockedVersions) { // add a separator diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs index 2303251d563..22872f830cc 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs @@ -339,6 +339,14 @@ public bool IsProjectPackageReference } } + public bool IsFloatingVersionSupported + { + get + { + return IsProjectPackageReference && !IsCentralPackageManagementEnabled; + } + } + public bool IsInstalledVersionTopLevel => InstalledVersion != null && PackageLevel == PackageLevel.TopLevel; public override IEnumerable GetSelectedProjects(UserAction action) diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs index 1319c9b49d5..962d4abb145 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs @@ -140,6 +140,8 @@ private async ValueTask InitializeAsync(PackageManagerModel model, INuGetUILogge ApplySettings(settings, Settings); _initialized = true; + await IsCentralPackageManagementEnabledAsync(CancellationToken.None); + NuGetExperimentationService = await ServiceLocator.GetComponentModelServiceAsync(); _isTransitiveDependenciesExperimentEnabled = NuGetExperimentationService.IsExperimentEnabled(ExperimentationConstants.TransitiveDependenciesInPMUI); @@ -567,6 +569,19 @@ await RunAndEmitRefreshAsync(async () => } } + private async Task IsCentralPackageManagementEnabledAsync(CancellationToken cancellationToken) + { + if (!Model.IsSolution) + { + await NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async delegate + { + // Go off the UI thread to perform non-UI operations + await TaskScheduler.Default; + _detailModel.IsCentralPackageManagementEnabled = await Model.Context.Projects.First().IsCentralPackageManagementEnabledAsync(Model.Context.ServiceBroker, cancellationToken); + }); + } + } + private async Task GetSettingsKeyAsync(CancellationToken cancellationToken) { string key; diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/ProjectView.xaml b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/ProjectView.xaml index 7645a7ed2d9..8ae35847529 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/ProjectView.xaml +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/ProjectView.xaml @@ -100,10 +100,10 @@ MinHeight="22" AutomationProperties.Name="{x:Static local:Resources.Label_Version}" AutomationProperties.LabeledBy="{Binding ElementName=SubscriptionsLabel}" - IsEditable="{Binding IsProjectPackageReference, Converter={StaticResource InverseNullToVisibilityConverter}}" - IsTextSearchEnabled="{Binding IsProjectPackageReference, Converter={StaticResource NotNullOrTrueToBooleanConverter}}" - IsReadOnly="{Binding IsProjectPackageReference, Converter={StaticResource NotNullOrTrueToBooleanConverter}}" - StaysOpenOnEdit="{Binding IsProjectPackageReference, Converter={StaticResource InverseNullToVisibilityConverter}}" + IsEditable="{Binding IsFloatingVersionSupported, Converter={StaticResource InverseNullToVisibilityConverter}}" + IsTextSearchEnabled="{Binding IsFloatingVersionSupported, Converter={StaticResource NotNullOrTrueToBooleanConverter}}" + IsReadOnly="{Binding IsFloatingVersionSupported, Converter={StaticResource NotNullOrTrueToBooleanConverter}}" + StaysOpenOnEdit="{Binding IsFloatingVersionSupported, Converter={StaticResource InverseNullToVisibilityConverter}}" ItemsSource="{Binding Path=VersionsView}" Text="{Binding Path=UserInput, Mode=OneWay}" SelectionChanged="Versions_SelectionChanged" diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Common/IProjectContextInfoExtensions.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Common/IProjectContextInfoExtensions.cs index 279bcd29054..14d802d2d46 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Common/IProjectContextInfoExtensions.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Common/IProjectContextInfoExtensions.cs @@ -12,6 +12,8 @@ using NuGet.Frameworks; using NuGet.Packaging.Core; using NuGet.VisualStudio.Internal.Contracts; +using NuGet.ProjectManagement.Projects; +using System.Linq; namespace NuGet.PackageManagement.VisualStudio { @@ -123,6 +125,21 @@ public static async ValueTask> GetTargetFram } } + public static async ValueTask IsCentralPackageManagementEnabledAsync(this IProjectContextInfo projectContextInfo, + IServiceBroker serviceBroker, + CancellationToken cancellationToken) + { + Assumes.NotNull(projectContextInfo); + Assumes.NotNull(serviceBroker); + + cancellationToken.ThrowIfCancellationRequested(); + + using (INuGetProjectManagerService projectManager = await GetProjectManagerAsync(serviceBroker, cancellationToken)) + { + return await projectManager.IsCentralPackageManagementEnabledAsync(projectContextInfo.ProjectId, cancellationToken); + } + } + public static async ValueTask GetMetadataAsync( this IProjectContextInfo projectContextInfo, IServiceBroker serviceBroker, diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Services/NuGetProjectManagerService.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Services/NuGetProjectManagerService.cs index 7560d54c867..7f20db8754d 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Services/NuGetProjectManagerService.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Services/NuGetProjectManagerService.cs @@ -274,6 +274,30 @@ public async ValueTask> GetTargetFrameworksA return targetFrameworks; } + public async ValueTask IsCentralPackageManagementEnabledAsync(string projectId, CancellationToken cancellationToken) + { + Assumes.NotNullOrEmpty(projectId); + + cancellationToken.ThrowIfCancellationRequested(); + + NuGetProject? project = await SolutionUtility.GetNuGetProjectAsync( + _sharedState.SolutionManager, + projectId, + cancellationToken); + + Assumes.NotNull(project); + + if (project is BuildIntegratedNuGetProject buildIntegratedProject) + { + var dgcContext = new DependencyGraphCacheContext(); + IReadOnlyList? packageSpecs = await buildIntegratedProject.GetPackageSpecsAsync(dgcContext); + + return packageSpecs.Any(spec => spec.RestoreMetadata.CentralPackageVersionsEnabled); + } + + return false; + } + public async ValueTask GetMetadataAsync(string projectId, CancellationToken cancellationToken) { Assumes.NotNullOrEmpty(projectId); diff --git a/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/INuGetProjectManagerService.cs b/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/INuGetProjectManagerService.cs index 297df389d27..e416b72514e 100644 --- a/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/INuGetProjectManagerService.cs +++ b/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/INuGetProjectManagerService.cs @@ -39,6 +39,7 @@ ValueTask> GetInstalledPackagesDepend string projectId, bool includeUnresolved, CancellationToken cancellationToken); + ValueTask IsCentralPackageManagementEnabledAsync(string projectId, CancellationToken cancellationToken); ValueTask GetMetadataAsync(string projectId, CancellationToken cancellationToken); ValueTask GetProjectAsync(string projectId, CancellationToken cancellationToken); ValueTask> GetProjectsAsync(CancellationToken cancellationToken); diff --git a/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/PublicAPI.Unshipped.txt b/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/PublicAPI.Unshipped.txt index 7dc5c58110b..f67bc33f764 100644 --- a/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/PublicAPI.Unshipped.txt +++ b/src/NuGet.Clients/NuGet.VisualStudio.Internal.Contracts/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +NuGet.VisualStudio.Internal.Contracts.INuGetProjectManagerService.IsCentralPackageManagementEnabledAsync(string! projectId, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask diff --git a/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/Services/NuGetProjectManagerServiceTests.cs b/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/Services/NuGetProjectManagerServiceTests.cs index 96a8048e56d..a0cb871b103 100644 --- a/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/Services/NuGetProjectManagerServiceTests.cs +++ b/test/NuGet.Clients.Tests/NuGet.PackageManagement.VisualStudio.Test/Services/NuGetProjectManagerServiceTests.cs @@ -1526,6 +1526,54 @@ private async Task GetPackageFoldersAsync_LegacyProjectWithFallbackFolder_Return Assert.Equal(2, folders.Count); } + [Theory] + [InlineData(true)] + [InlineData(false)] + private async Task GetCentralPackageVersionsManagmentEnabled_SucceedsAsync(bool isCentralPackageVersionsEnabled) + { + string projectName = Guid.NewGuid().ToString(); + string projectId = projectName; + var projectSystemCache = new ProjectSystemCache(); + IVsProjectAdapter projectAdapter = Mock.Of(); + + using var pathContext = new SimpleTestPathContext(); + Initialize(); + + // Prepare: Create project + string projectFullPath = Path.Combine(pathContext.SolutionRoot, projectName, $"{projectName}.csproj"); + + CpsPackageReferenceProject prProject = CreateCpsPackageReferenceProject(projectName, projectFullPath, projectSystemCache); + + ProjectNames projectNames = GetTestProjectNames(projectFullPath, projectName); + string referenceSpec = $@" + {{ + ""frameworks"": + {{ + ""net6.0"": + {{ + ""dependencies"": + {{ + }} + }} + }} + }}"; + PackageSpec packageSpec = JsonPackageSpecReader.GetPackageSpec(referenceSpec, projectName, projectFullPath).WithTestRestoreMetadata(); + packageSpec.RestoreMetadata.CentralPackageVersionsEnabled = isCentralPackageVersionsEnabled; + + // Restore info + DependencyGraphSpec projectRestoreInfo = ProjectTestHelpers.GetDGSpecFromPackageSpecs(packageSpec); + projectSystemCache.AddProjectRestoreInfo(projectNames, projectRestoreInfo, new List()); + projectSystemCache.AddProject(projectNames, projectAdapter, prProject).Should().BeTrue(); + + _solutionManager.NuGetProjects.Add(prProject); + + // Act + bool isCentralPackageManagmentEnabled = await _projectManager.IsCentralPackageManagementEnabledAsync(projectId, CancellationToken.None); + + // Assert + Assert.Equal(isCentralPackageVersionsEnabled, isCentralPackageManagmentEnabled); + } + [Fact] private async Task GetPackageFoldersAsync_CpsProjectWithFallbackFolder_ReturnsPackageFoldersAsync() {