From 9415900b1bcd8f07d72bf37a7eeacf1b1ac63a0b Mon Sep 17 00:00:00 2001 From: Pato Beltran Date: Thu, 28 Sep 2017 15:32:53 -0700 Subject: [PATCH] WIP: use resolved version of package from assets file --- .../Models/PackageDetailControlModel.cs | 8 +- .../Xamls/PackageManagerControl.xaml.cs | 8 ++ .../Projects/LegacyPackageReferenceProject.cs | 22 +++-- .../NetCorePackageReferenceProject.cs | 31 +++++-- .../Projects/ProjectKNuGetProject.cs | 20 +++-- .../Extensibility/VsPathContextProvider.cs | 4 +- .../Utils/InstalledPackageEnumerator.cs | 6 +- .../BuildIntegratedPackageReference.cs | 14 +-- .../BuildIntegratedRestoreUtility.cs | 2 +- .../DependencyVersionLookup.cs | 86 +++++++++++++++++++ .../Projects/BuildIntegratedNuGetProject.cs | 25 ++++++ .../Projects/INuGetIntegratedProject.cs | 14 ++- .../Projects/ProjectJsonNuGetProject.cs | 10 ++- .../Projects/ProjectKNuGetProject.cs | 32 ++++++- ...Utility.cs => IntegratedProjectUtility.cs} | 69 ++++++++++++--- .../NuGet.Versioning/VersionRange.cs | 13 ++- .../NuGet.Versioning/VersionRangeFactory.cs | 5 +- .../BuildIntegratedNuGetProjectTests.cs | 4 +- 18 files changed, 312 insertions(+), 61 deletions(-) create mode 100644 src/NuGet.Core/NuGet.PackageManagement/DependencyVersionLookup.cs rename src/NuGet.Core/NuGet.PackageManagement/Utility/{BuildIntegratedProjectUtility.cs => IntegratedProjectUtility.cs} (60%) diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs index 1eaaf349305..92604878802 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/PackageDetailControlModel.cs @@ -99,13 +99,13 @@ public override void CleanUp() protected override void CreateVersions() { _versions = new List(); - var installedDependency = InstalledPackageDependencies.Where(p => - StringComparer.OrdinalIgnoreCase.Equals(p.Id, Id) && p.VersionRange != null && p.VersionRange.HasLowerBound) - .OrderByDescending(p => p.VersionRange.MinVersion) + var installedDependency = InstalledPackages.Where(p => + StringComparer.OrdinalIgnoreCase.Equals(p.Id, Id)) + .OrderByDescending(p => p.Version) .FirstOrDefault(); // installVersion is null if the package is not installed - var installedVersion = installedDependency?.VersionRange?.MinVersion; + var installedVersion = installedDependency?.Version; var allVersions = _allPackageVersions?.OrderByDescending(v => v).ToArray(); 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 189055e3640..5698ffda8d5 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageManagerControl.xaml.cs @@ -17,6 +17,7 @@ using NuGet.PackageManagement.VisualStudio; using NuGet.Packaging.Core; using NuGet.ProjectManagement; +using NuGet.ProjectManagement.Projects; using NuGet.Protocol.Core.Types; using NuGet.Resolver; using NuGet.Versioning; @@ -99,6 +100,7 @@ public PackageManagerControl( _uiLogger = uiLogger; Model = model; Settings = nugetSettings; + if (!Model.IsSolution) { _detailModel = new PackageDetailControlModel(Model.Context.SolutionManager, Model.Context.Projects); @@ -627,6 +629,12 @@ private void SearchPackagesAndRefreshUpdateCount(string searchText, bool useCach { NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(async () => { + foreach (var project in Model.Context.Projects) + { + // Read the dependencies out off the latest asset file + await Task.Run(() => DependencyVersionLookup.LoadAssetsFileAndCreateLookupForProjectAsync(project as INuGetIntegratedProject)); + } + await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var loadContext = new PackageLoadContext(ActiveSources, Model.IsSolution, Model.Context); diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs index f6541c19ca2..5099f4b7257 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/LegacyPackageReferenceProject.cs @@ -43,7 +43,8 @@ public LegacyPackageReferenceProject( IVsProjectAdapter vsProjectAdapter, string projectId, INuGetProjectServices projectServices, - IVsProjectThreadingService threadingService) + IVsProjectThreadingService threadingService) : + base() { Assumes.Present(vsProjectAdapter); Assumes.NotNullOrEmpty(projectId); @@ -131,7 +132,7 @@ public override async Task> GetPackageSpecsAsync(Depe public override async Task> GetInstalledPackagesAsync(CancellationToken token) { // Settings are not needed for this purpose, this only finds the installed packages - return GetPackageReferences(await GetPackageSpecAsync(NullSettings.Instance)); + return GetPackageReferences(await GetPackageSpecAsync(NullSettings.Instance), _dependencyVersionLookup); } public override async Task InstallPackageAsync( @@ -251,30 +252,35 @@ private IList GetConfigFilePaths(ISettings settings) return SettingsUtility.GetConfigFilePaths(settings).ToList(); } - private static PackageReference[] GetPackageReferences(PackageSpec packageSpec) + private static PackageReference[] GetPackageReferences(PackageSpec packageSpec, DependencyVersionLookup lookup) { var frameworkSorter = new NuGetFrameworkSorter(); return packageSpec .TargetFrameworks - .SelectMany(f => GetPackageReferences(f.Dependencies, f.FrameworkName)) + .SelectMany(f => GetPackageReferences(f.Dependencies, f.FrameworkName, lookup)) .GroupBy(p => p.PackageIdentity) .Select(g => g.OrderBy(p => p.TargetFramework, frameworkSorter).First()) .ToArray(); } - private static IEnumerable GetPackageReferences(IEnumerable libraries, NuGetFramework targetFramework) + private static IEnumerable GetPackageReferences(IEnumerable libraries, NuGetFramework targetFramework, DependencyVersionLookup lookup) { return libraries .Where(l => l.LibraryRange.TypeConstraint == LibraryDependencyTarget.Package) - .Select(l => ToPackageReference(l, targetFramework)); + .Select(l => ToPackageReference(l, targetFramework, lookup)); } - private static PackageReference ToPackageReference(LibraryDependency library, NuGetFramework targetFramework) + private static PackageReference ToPackageReference(LibraryDependency library, NuGetFramework targetFramework, DependencyVersionLookup lookup) { + NuGetVersion version; + if (!lookup.TryGet(library.LibraryRange.Name, out version)) + { + version = library.LibraryRange.VersionRange.MinVersion; + } var identity = new PackageIdentity( library.LibraryRange.Name, - library.LibraryRange.VersionRange.MinVersion); + version); return new PackageReference(identity, targetFramework); } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/NetCorePackageReferenceProject.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/NetCorePackageReferenceProject.cs index 1aff76c324b..9171d026c10 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/NetCorePackageReferenceProject.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/NetCorePackageReferenceProject.cs @@ -51,7 +51,8 @@ public NetCorePackageReferenceProject( IVsProjectAdapter vsProjectAdapter, UnconfiguredProject unconfiguredProject, INuGetProjectServices projectServices, - string projectId) + string projectId) : + base() { Assumes.Present(projectFullPath); Assumes.Present(projectSystemCache); @@ -66,6 +67,7 @@ public NetCorePackageReferenceProject( _projectSystemCache = projectSystemCache; _vsProjectAdapter = vsProjectAdapter; _unconfiguredProject = unconfiguredProject; + _dependencyVersionLookup = new DependencyVersionLookup(); ProjectServices = projectServices; InternalMetadata.Add(NuGetProjectMetadataKeys.Name, _projectName); @@ -190,7 +192,7 @@ public override Task> GetInstalledPackagesAsync(Ca var packageSpec = GetPackageSpec(); if (packageSpec != null) { - installedPackages = GetPackageReferences(packageSpec); + installedPackages = GetPackageReferences(packageSpec, _dependencyVersionLookup); } else { @@ -200,23 +202,23 @@ public override Task> GetInstalledPackagesAsync(Ca return Task.FromResult>(installedPackages); } - private static PackageReference[] GetPackageReferences(PackageSpec packageSpec) + private static PackageReference[] GetPackageReferences(PackageSpec packageSpec, DependencyVersionLookup lookup) { var frameworkSorter = new NuGetFrameworkSorter(); return packageSpec .TargetFrameworks - .SelectMany(f => GetPackageReferences(f.Dependencies, f.FrameworkName)) + .SelectMany(f => GetPackageReferences(f.Dependencies, f.FrameworkName, lookup)) .GroupBy(p => p.PackageIdentity) .Select(g => g.OrderBy(p => p.TargetFramework, frameworkSorter).First()) .ToArray(); } - private static IEnumerable GetPackageReferences(IEnumerable libraries, NuGetFramework targetFramework) + private static IEnumerable GetPackageReferences(IEnumerable libraries, NuGetFramework targetFramework, DependencyVersionLookup lookup) { return libraries .Where(l => l.LibraryRange.TypeConstraint == LibraryDependencyTarget.Package) - .Select(l => new BuildIntegratedPackageReference(l, targetFramework)); + .Select(l => new BuildIntegratedPackageReference(l, targetFramework, lookup)); } public override async Task InstallPackageAsync( @@ -276,8 +278,21 @@ await conditionalService.AddAsync( // This is the update operation if (!result.Added) { - var existingReference = result.Reference; - await existingReference.Metadata.SetPropertyValueAsync("Version", formattedRange); + var existingReferenceMetadata = result.Reference.Metadata; + var currentVersionString = await existingReferenceMetadata.GetEvaluatedPropertyValueAsync("Version"); + var currentVersion = VersionRange.Parse(currentVersionString); + + // Only update the package reference if any: + // 1. The current version is not floating or a range + // 2. The current version is a range and the new version is not a subset of it + // 3. The current version is a float and the new version doesn't satisfies it + var currentIsSingleVersion = (!currentVersion.IsFloating && !currentVersion.WasOriginallyRange); + var currentIsRangeAndNewIsNotSubset = currentVersion.WasOriginallyRange && !range.IsSubSetOrEqualTo(currentVersion); + var currentIsFloatAndNewDoesNotSatisfies = currentVersion.IsFloating && !currentVersion.Float.Satisfies(range.MinVersion); + if (currentIsSingleVersion || currentIsRangeAndNewIsNotSubset || currentIsFloatAndNewDoesNotSatisfies) + { + await existingReferenceMetadata.SetPropertyValueAsync("Version", formattedRange); + } } } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/ProjectKNuGetProject.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/ProjectKNuGetProject.cs index 316549337e7..f42f68fcb3e 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/ProjectKNuGetProject.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/ProjectKNuGetProject.cs @@ -61,7 +61,7 @@ public override Task PreProcessAsync(INuGetProjectContext nuGetProjectContext, C } [SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD002", Justification = "NuGet/Home#4833 Baseline")] - public ProjectKNuGetProject(INuGetPackageManager project, string projectName, string uniqueName, string projectId) + public ProjectKNuGetProject(INuGetPackageManager project, string projectName, string uniqueName, string projectId) : base() { _project = project; InternalMetadata.Add(NuGetProjectMetadataKeys.Name, projectName); @@ -187,13 +187,17 @@ public override async Task> GetInstalledPackagesAs var moniker = item as INuGetPackageMoniker; if (moniker != null) { - // As with build integrated projects (UWP project.json), treat the version as a - // version range and use the minimum version of that range. Eventually, this - // method should return VersionRange instances, not NuGetVersion so that the - // UI can express the full project.json intent. This improvement is tracked - // here: https://github.com/NuGet/Home/issues/3101 - var versionRange = VersionRange.Parse(moniker.Version); - var version = versionRange.MinVersion; + NuGetVersion version; + if (!_dependencyVersionLookup.TryGet(moniker.Id, out version)) + { + // As with build integrated projects (UWP project.json), treat the version as a + // version range and use the minimum version of that range. Eventually, this + // method should return VersionRange instances, not NuGetVersion so that the + // UI can express the full project.json intent. This improvement is tracked + // here: https://github.com/NuGet/Home/issues/3101 + var versionRange = VersionRange.Parse(moniker.Version); + version = versionRange.MinVersion; + } identity = new PackageIdentity(moniker.Id, version); } diff --git a/src/NuGet.Clients/NuGet.VisualStudio.Implementation/Extensibility/VsPathContextProvider.cs b/src/NuGet.Clients/NuGet.VisualStudio.Implementation/Extensibility/VsPathContextProvider.cs index 8af7d8806f2..24188ee2d7b 100644 --- a/src/NuGet.Clients/NuGet.VisualStudio.Implementation/Extensibility/VsPathContextProvider.cs +++ b/src/NuGet.Clients/NuGet.VisualStudio.Implementation/Extensibility/VsPathContextProvider.cs @@ -67,7 +67,7 @@ public VsPathContextProvider( _settings = settings; _solutionManager = solutionManager; _logger = logger; - _getLockFileOrNullAsync = BuildIntegratedProjectUtility.GetLockFileOrNull; + _getLockFileOrNullAsync = IntegratedProjectUtility.GetLockFileOrNull; _dte = new AsyncLazy( async () => @@ -105,7 +105,7 @@ public VsPathContextProvider( _settings = new Lazy(() => settings); _solutionManager = new Lazy(() => solutionManager); _logger = new Lazy(() => logger); - _getLockFileOrNullAsync = getLockFileOrNullAsync ?? BuildIntegratedProjectUtility.GetLockFileOrNull; + _getLockFileOrNullAsync = getLockFileOrNullAsync ?? IntegratedProjectUtility.GetLockFileOrNull; } public bool TryCreateContext(string projectUniqueName, out IVsPathContext outputPathContext) diff --git a/src/NuGet.Clients/NuGetConsole.Host.PowerShell/Utils/InstalledPackageEnumerator.cs b/src/NuGet.Clients/NuGetConsole.Host.PowerShell/Utils/InstalledPackageEnumerator.cs index 35cd25201ae..7708c4d8a0c 100644 --- a/src/NuGet.Clients/NuGetConsole.Host.PowerShell/Utils/InstalledPackageEnumerator.cs +++ b/src/NuGet.Clients/NuGetConsole.Host.PowerShell/Utils/InstalledPackageEnumerator.cs @@ -97,7 +97,7 @@ public InstalledPackageEnumerator( _solutionManager = solutionManager; _settings = settings; - _getLockFileOrNullAsync = BuildIntegratedProjectUtility.GetLockFileOrNull; + _getLockFileOrNullAsync = IntegratedProjectUtility.GetLockFileOrNull; } /// @@ -113,7 +113,7 @@ internal InstalledPackageEnumerator( _solutionManager = solutionManager; _settings = settings; - _getLockFileOrNullAsync = getLockFileOrNullAsync ?? BuildIntegratedProjectUtility.GetLockFileOrNull; + _getLockFileOrNullAsync = getLockFileOrNullAsync ?? IntegratedProjectUtility.GetLockFileOrNull; } public async Task> EnumeratePackagesAsync( @@ -324,7 +324,7 @@ private async Task CollectPackagesForBuildIntegratedProjectAsync( fppr = new FallbackPackagePathResolver(pathContext); } - foreach (var package in BuildIntegratedProjectUtility.GetOrderedLockFilePackageDependencies(lockFile)) + foreach (var package in IntegratedProjectUtility.GetLockFilePackageDependencies(lockFile, true)) { if (!finishedPackages.Contains(package)) { diff --git a/src/NuGet.Core/NuGet.PackageManagement/BuildIntegratedPackageReference.cs b/src/NuGet.Core/NuGet.PackageManagement/BuildIntegratedPackageReference.cs index cd72f9232c4..f9b462a3244 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/BuildIntegratedPackageReference.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/BuildIntegratedPackageReference.cs @@ -24,8 +24,8 @@ public class BuildIntegratedPackageReference : PackageReference /// Create a PackageReference based on a LibraryDependency. /// /// Full PackageReference metadata. - public BuildIntegratedPackageReference(LibraryDependency dependency, NuGetFramework projectFramework) - : base(GetIdentity(dependency), + public BuildIntegratedPackageReference(LibraryDependency dependency, NuGetFramework projectFramework, DependencyVersionLookup lookup) + : base(GetIdentity(dependency, lookup), targetFramework: projectFramework, userInstalled: true, developmentDependency: dependency?.SuppressParent == LibraryIncludeFlags.All, @@ -43,15 +43,19 @@ public BuildIntegratedPackageReference(LibraryDependency dependency, NuGetFramew /// /// Convert range to a PackageIdentity /// - private static PackageIdentity GetIdentity(LibraryDependency dependency) + private static PackageIdentity GetIdentity(LibraryDependency dependency, DependencyVersionLookup lookup) { if (dependency == null) { throw new ArgumentNullException(nameof(dependency)); } - // MinVersion may not exist for ranges such as ( , 2.0.0]; - var version = dependency.LibraryRange?.VersionRange?.MinVersion ?? new NuGetVersion(0, 0, 0); + NuGetVersion version; + if (!lookup.TryGet(dependency.Name, out version)) + { + // MinVersion may not exist for ranges such as ( , 2.0.0]; + version = dependency.LibraryRange?.VersionRange?.MinVersion ?? new NuGetVersion(0, 0, 0); + } return new PackageIdentity(dependency.Name, version); } diff --git a/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/BuildIntegratedRestoreUtility.cs b/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/BuildIntegratedRestoreUtility.cs index 594ca2bf000..cd5999079ef 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/BuildIntegratedRestoreUtility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/BuildIntegration/BuildIntegratedRestoreUtility.cs @@ -30,7 +30,7 @@ public static async Task ExecuteInitPs1ScriptsAsync( CancellationToken token) { // Find all dependencies in sorted order - var sortedPackages = await BuildIntegratedProjectUtility.GetOrderedProjectPackageDependencies(project); + var sortedPackages = await IntegratedProjectUtility.GetProjectPackageDependenciesAsync(project, true); // Keep track of the packages that need to be executed. var packagesToExecute = new HashSet(packages, PackageIdentity.Comparer); diff --git a/src/NuGet.Core/NuGet.PackageManagement/DependencyVersionLookup.cs b/src/NuGet.Core/NuGet.PackageManagement/DependencyVersionLookup.cs new file mode 100644 index 00000000000..87e0bc5a85e --- /dev/null +++ b/src/NuGet.Core/NuGet.PackageManagement/DependencyVersionLookup.cs @@ -0,0 +1,86 @@ +// 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.Tasks; +using NuGet.ProjectManagement.Projects; +using NuGet.Versioning; + +namespace NuGet.ProjectManagement +{ + public class DependencyVersionLookup + { + private DateTime? _assetsFileCreatedAt; + private Dictionary _versionLookup; + + public DependencyVersionLookup() + { + _versionLookup = new Dictionary(); + _assetsFileCreatedAt = null; + } + + public void Update(Dictionary newLookup, DateTime createdAt) + { + _versionLookup = newLookup; + _assetsFileCreatedAt = createdAt; + } + + public bool TryGet(string packageId, out NuGetVersion version) + { + version = null; + if (_versionLookup == null ||!_versionLookup.Any()) + { + return false; + } + return _versionLookup.TryGetValue(packageId, out version); + } + + public bool AssetsFileHasBeenRead() + { + return _assetsFileCreatedAt != null; + } + + public bool AssetsFileHasChanged(DateTime assetsFileTimestamp) + { + return _assetsFileCreatedAt.Equals(assetsFileTimestamp); + } + + // This method reads the assets file and constructs the appropriate object with references + // to the installed dependencies found there. It only reads the assets file if it hasn't + // changed or it hasn't been read before. + public static async Task LoadAssetsFileAndCreateLookupForProjectAsync(INuGetIntegratedProject project) + { + if (project != null) + { + var assetFilechanged = false; + DateTime? assetsFileTimestamp = null; + if (project.Lookup.AssetsFileHasBeenRead()) + { + assetsFileTimestamp = await project.GetAssetsFileTimestampIFExistsAsync(); + if (assetsFileTimestamp != null) + { + assetFilechanged = project.Lookup.AssetsFileHasChanged(assetsFileTimestamp.Value); + } + } + + // We should only read the assets file if we haven't read it before or if the file changed + if (!project.Lookup.AssetsFileHasBeenRead() || assetFilechanged) + { + // If there is no assets file this will return an empty list + var dependencies = await project.GetTopLevelDependencies(); + if (dependencies != null && dependencies.Any()) + { + if (assetsFileTimestamp == null) + { + assetsFileTimestamp = await project.GetAssetsFileTimestampIFExistsAsync(); + } + // If we are targeting multiple frameworks we should get the Min version to show (WIP: Add to spec and ask for feedback) + project.Lookup.Update(dependencies.GroupBy(item => item.Id).ToDictionary(x => x.Key, x => x.Min(y => y.Version)), assetsFileTimestamp.Value); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/BuildIntegratedNuGetProject.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/BuildIntegratedNuGetProject.cs index da45edb2931..c39aa81b4ef 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/BuildIntegratedNuGetProject.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/BuildIntegratedNuGetProject.cs @@ -29,6 +29,31 @@ public abstract class BuildIntegratedNuGetProject { protected BuildIntegratedNuGetProject() { + _dependencyVersionLookup = new DependencyVersionLookup(); + } + + protected DependencyVersionLookup _dependencyVersionLookup; + public DependencyVersionLookup Lookup => _dependencyVersionLookup; + + /// + /// Gets timestamp of assets file if exists, if not return null + /// + public async Task GetAssetsFileTimestampIFExistsAsync() + { + var lockFilePath = await GetAssetsFilePathOrNullAsync(); + + if (lockFilePath == null || !File.Exists(lockFilePath)) + { + return null; + } + + var lockFileInfo = new FileInfo(lockFilePath); + return lockFileInfo.CreationTime; + } + + public Task> GetTopLevelDependencies() + { + return IntegratedProjectUtility.GetProjectPackageDependenciesAsync(this, false); } /// diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/INuGetIntegratedProject.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/INuGetIntegratedProject.cs index a8598df22f5..8f3de225f48 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/INuGetIntegratedProject.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/INuGetIntegratedProject.cs @@ -1,6 +1,11 @@ -// 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; +using System.Collections.Generic; +using System.Threading.Tasks; +using NuGet.Packaging.Core; + namespace NuGet.ProjectManagement.Projects { /// @@ -8,5 +13,12 @@ namespace NuGet.ProjectManagement.Projects /// public interface INuGetIntegratedProject { + DependencyVersionLookup Lookup { get; } + + Task GetAssetsFilePathOrNullAsync(); + + Task GetAssetsFileTimestampIFExistsAsync(); + + Task> GetTopLevelDependencies(); } } diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectJsonNuGetProject.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectJsonNuGetProject.cs index 597ee093dc0..7ae892625ce 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectJsonNuGetProject.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectJsonNuGetProject.cs @@ -40,7 +40,8 @@ public class ProjectJsonNuGetProject : BuildIntegratedNuGetProject /// Path to the msbuild project file. public ProjectJsonNuGetProject( string jsonConfig, - string msBuildProjectPath) + string msBuildProjectPath) : + base() { if (jsonConfig == null) { @@ -137,7 +138,12 @@ public override async Task> GetInstalledPackagesAs foreach (var dependency in JsonConfigUtility.GetDependencies(await GetJsonAsync())) { // Use the minimum version of the range for the identity - var identity = new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion); + NuGetVersion version; + if (!_dependencyVersionLookup.TryGet(dependency.Id, out version)) + { + version = dependency.VersionRange.MinVersion; + } + var identity = new PackageIdentity(dependency.Id, version); // Pass the actual version range as the allowed range packages.Add(new PackageReference(identity, diff --git a/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectKNuGetProject.cs b/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectKNuGetProject.cs index 7a98c8ff52e..7140c99ed27 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectKNuGetProject.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Projects/ProjectKNuGetProject.cs @@ -1,10 +1,40 @@ -// 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; +using System.Collections.Generic; +using System.Threading.Tasks; +using NuGet.Packaging.Core; namespace NuGet.ProjectManagement.Projects { public abstract class ProjectKNuGetProjectBase : NuGetProject, INuGetIntegratedProject { + protected DependencyVersionLookup _dependencyVersionLookup; + public DependencyVersionLookup Lookup => _dependencyVersionLookup; + + protected ProjectKNuGetProjectBase() + { + _dependencyVersionLookup = new DependencyVersionLookup(); + } + + // TODO-PATO: Implement this + public Task GetAssetsFileTimestampIFExistsAsync() + { + return null; + } + + // TODO-PATO: Implement this + public Task> GetTopLevelDependencies() + { + return null; + } + + // TODO-PATO: Implement this + public Task GetAssetsFilePathOrNullAsync() + { + return null; + } + } } diff --git a/src/NuGet.Core/NuGet.PackageManagement/Utility/BuildIntegratedProjectUtility.cs b/src/NuGet.Core/NuGet.PackageManagement/Utility/IntegratedProjectUtility.cs similarity index 60% rename from src/NuGet.Core/NuGet.PackageManagement/Utility/BuildIntegratedProjectUtility.cs rename to src/NuGet.Core/NuGet.PackageManagement/Utility/IntegratedProjectUtility.cs index 8d2319ce064..d1b53d40748 100644 --- a/src/NuGet.Core/NuGet.PackageManagement/Utility/BuildIntegratedProjectUtility.cs +++ b/src/NuGet.Core/NuGet.PackageManagement/Utility/IntegratedProjectUtility.cs @@ -1,36 +1,36 @@ -// 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.Collections.Generic; using System.IO; using System.Linq; -using NuGet.Common; +using System.Threading.Tasks; using NuGet.LibraryModel; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.ProjectManagement.Projects; using NuGet.ProjectModel; -using System.Threading.Tasks; + namespace NuGet.ProjectManagement { /// /// Utilities for project.json /// - public static class BuildIntegratedProjectUtility + public static class IntegratedProjectUtility { /// - /// Orders all package dependencies in a project. + /// Gets all package dependencies in a project either sorted or not. /// Project must be restored. /// - public static async Task> GetOrderedProjectPackageDependencies( - BuildIntegratedNuGetProject buildIntegratedProject) + public static async Task> GetProjectPackageDependenciesAsync( + INuGetIntegratedProject integratedProject, bool sorted) { - var lockFile = await GetLockFileOrNull(buildIntegratedProject); + var lockFile = await GetLockFileOrNull(integratedProject); if (lockFile != null) { - return GetOrderedLockFilePackageDependencies(lockFile); + return GetLockFilePackageDependencies(lockFile, sorted); } return new List(); @@ -39,9 +39,9 @@ public static async Task> GetOrderedProjectPackag /// /// Read lock file /// - public static async Task GetLockFileOrNull(BuildIntegratedNuGetProject buildIntegratedProject) + public static async Task GetLockFileOrNull(INuGetIntegratedProject integratedProject) { - var lockFilePath = await buildIntegratedProject.GetAssetsFilePathOrNullAsync(); + var lockFilePath = await integratedProject.GetAssetsFilePathOrNullAsync(); if (lockFilePath == null) { @@ -71,14 +71,57 @@ public static LockFile GetLockFileOrNull(string lockFilePath) /// /// Lock file dependencies - packages only /// - public static IReadOnlyList GetOrderedLockFilePackageDependencies(LockFile lockFile) + public static IReadOnlyList GetLockFilePackageDependencies(LockFile lockFile, bool sorted) { - return GetOrderedLockFileDependencies(lockFile) + IReadOnlyList dependencies; + if (sorted) + { + dependencies = GetOrderedLockFileDependencies(lockFile); + } + else + { + dependencies = GetLockFileDependencies(lockFile); + } + + return dependencies .Where(library => library.Type == LibraryType.Package) .Select(library => new PackageIdentity(library.Name, library.Version)) .ToList(); } + /// + /// Get dependencies from the lock file + /// + /// + /// + public static IReadOnlyList GetLockFileDependencies(LockFile lockFile) + { + var results = new List(); + var typeMappings = new Dictionary(PackageIdentity.Comparer); + + foreach (var target in lockFile.Targets) + { + foreach (var targetLibrary in target.Libraries) + { + var identity = new PackageIdentity(targetLibrary.Name, targetLibrary.Version); + var dependency = new PackageDependencyInfo(identity, targetLibrary.Dependencies); + + if (!typeMappings.ContainsKey(dependency)) + { + var libraryIdentity = new LibraryIdentity( + targetLibrary.Name, + targetLibrary.Version, + LibraryType.Parse(targetLibrary.Type)); + + results.Add(libraryIdentity); + typeMappings.Add(dependency, libraryIdentity); + } + } + } + + return results; + } + /// /// Get ordered dependencies from the lock file /// diff --git a/src/NuGet.Core/NuGet.Versioning/VersionRange.cs b/src/NuGet.Core/NuGet.Versioning/VersionRange.cs index 9ee880eb259..712b4ae28be 100644 --- a/src/NuGet.Core/NuGet.Versioning/VersionRange.cs +++ b/src/NuGet.Core/NuGet.Versioning/VersionRange.cs @@ -14,6 +14,7 @@ public partial class VersionRange : VersionRangeBase, IFormattable { private readonly FloatRange _floatRange; private readonly string _originalString; + private readonly bool _wasOriginallyRange; /// /// Creates a range that is greater than or equal to the minVersion. @@ -36,7 +37,8 @@ public VersionRange(NuGetVersion minVersion, FloatRange floatRange) maxVersion: null, includeMaxVersion: false, originalString: null, - floatRange: floatRange) + floatRange: floatRange, + wasOriginallyRange: false) { } @@ -57,12 +59,14 @@ public VersionRange(VersionRange range, FloatRange floatRange) /// True if maxVersion satisfies the condition. /// The floating range subset used to find the best version match. /// The original string being parsed to this object. + /// True if the original string was in range format. public VersionRange(NuGetVersion minVersion = null, bool includeMinVersion = true, NuGetVersion maxVersion = null, - bool includeMaxVersion = false, FloatRange floatRange = null, string originalString = null) + bool includeMaxVersion = false, FloatRange floatRange = null, string originalString = null, bool wasOriginallyRange = false) : base(minVersion, includeMinVersion, maxVersion, includeMaxVersion) { _floatRange = floatRange; _originalString = originalString; + _wasOriginallyRange = wasOriginallyRange; } /// @@ -89,6 +93,11 @@ public string OriginalString get { return _originalString; } } + /// + /// Indicates if the original string was a range before parsing + /// + public bool WasOriginallyRange => _wasOriginallyRange; + /// /// Normalized range string. /// diff --git a/src/NuGet.Core/NuGet.Versioning/VersionRangeFactory.cs b/src/NuGet.Core/NuGet.Versioning/VersionRangeFactory.cs index 60fb66aaa46..b6995478905 100644 --- a/src/NuGet.Core/NuGet.Versioning/VersionRangeFactory.cs +++ b/src/NuGet.Core/NuGet.Versioning/VersionRangeFactory.cs @@ -116,6 +116,7 @@ public static bool TryParse(string value, bool allowFloating, out VersionRange v NuGetVersion minVersion = null; NuGetVersion maxVersion = null; FloatRange floatRange = null; + var originallyRange = false; if (charArray[0] == '(' || charArray[0] == '[') @@ -178,6 +179,7 @@ public static bool TryParse(string value, bool allowFloating, out VersionRange v // If there is only one piece, we use it for both min and max minVersionString = parts[0]; maxVersionString = (parts.Length == 2) ? parts[1] : parts[0]; + originallyRange = true; } else { @@ -233,7 +235,8 @@ public static bool TryParse(string value, bool allowFloating, out VersionRange v maxVersion: maxVersion, includeMaxVersion: isMaxInclusive, floatRange: floatRange, - originalString: value); + originalString: value, + wasOriginallyRange: originallyRange); return true; } diff --git a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/ProjectManagement/BuildIntegratedNuGetProjectTests.cs b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/ProjectManagement/BuildIntegratedNuGetProjectTests.cs index 7a55c5ae842..fc5eaeb1208 100644 --- a/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/ProjectManagement/BuildIntegratedNuGetProjectTests.cs +++ b/test/NuGet.Core.Tests/NuGet.PackageManagement.Test/ProjectManagement/BuildIntegratedNuGetProjectTests.cs @@ -100,7 +100,7 @@ public void BuildIntegratedNuGetProject_SortDependenciesWithProjects() target.Libraries.Add(targetB); // Act - var ordered = BuildIntegratedProjectUtility.GetOrderedLockFileDependencies(lockFile) + var ordered = IntegratedProjectUtility.GetOrderedLockFileDependencies(lockFile) .OrderBy(lib => lib.Name, StringComparer.Ordinal) .ToList(); @@ -147,7 +147,7 @@ public void BuildIntegratedNuGetProject_SortDependenciesWithProjects_GetPackages target.Libraries.Add(targetB); // Act - var ordered = BuildIntegratedProjectUtility.GetOrderedLockFilePackageDependencies(lockFile) + var ordered = IntegratedProjectUtility.GetLockFilePackageDependencies(lockFile, true) .OrderBy(lib => lib.Id, StringComparer.Ordinal) .ToList();