| File: RestoreCommand\Utility\NoOpRestoreUtilities.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Commands\NuGet.Commands.csproj (NuGet.Commands) |
// 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. #nullable disable using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using NuGet.Common; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.ProjectModel; using NuGet.Protocol; using NuGet.Shared; using NuGet.Versioning; namespace NuGet.Commands { public static class NoOpRestoreUtilities { /// <summary> /// The name of the file to use. When changing this, you should also change <see cref="LockFileFormat.AssetsFileName"/>. /// </summary> internal const string NoOpCacheFileName = "project.nuget.cache"; /// <summary> /// If the dependencyGraphSpec is not set, we cannot no-op on this project restore. /// </summary> internal static bool IsNoOpSupported(RestoreRequest request) { return request.DependencyGraphSpec != null; } public static string GetProjectCacheFilePath(string cacheRoot, string projectPath) { return GetProjectCacheFilePath(cacheRoot); } public static string GetProjectCacheFilePath(string cacheRoot) { return cacheRoot == null ? null : Path.Combine(cacheRoot, NoOpCacheFileName); } internal static string GetToolCacheFilePath(RestoreRequest request, LockFile lockFile) { if (request.ProjectStyle == ProjectStyle.DotnetCliTool && lockFile != null) { var toolName = ToolRestoreUtility.GetToolIdOrNullFromSpec(request.Project); var lockFileLibrary = ToolRestoreUtility.GetToolTargetLibrary(lockFile, toolName); if (lockFileLibrary != null) { var version = lockFileLibrary.Version; var toolPathResolver = new ToolPathResolver(request.PackagesDirectory); return GetToolCacheFilePath(toolPathResolver.GetToolDirectoryPath( toolName, version, lockFile.Targets.First().TargetFramework), toolName); } } return null; } internal static string GetToolCacheFilePath(string toolDirectory, string toolName) { return Path.Combine( toolDirectory, $"{toolName.ToLowerInvariant()}.nuget.cache"); } /// <summary> /// Evaluate the location of the cache file path, based on ProjectStyle. /// </summary> internal static string GetCacheFilePath(RestoreRequest request, LockFile lockFile) { var projectCacheFilePath = request.Project.RestoreMetadata?.CacheFilePath; if (string.IsNullOrEmpty(projectCacheFilePath)) { if (request.ProjectStyle == ProjectStyle.PackageReference || request.ProjectStyle == ProjectStyle.ProjectJson) { var cacheRoot = request.MSBuildProjectExtensionsPath ?? request.RestoreOutputPath; projectCacheFilePath = request.Project.RestoreMetadata.CacheFilePath = GetProjectCacheFilePath(cacheRoot); } else if (request.ProjectStyle == ProjectStyle.DotnetCliTool) { projectCacheFilePath = GetToolCacheFilePath(request, lockFile); } } return projectCacheFilePath != null ? Path.GetFullPath(projectCacheFilePath) : null; } /// <summary> /// This method verifies that the assets files, props/targets files and all the packages written out in the assets file are present on disk /// When the project has opted into packages lock file, it also verified that the lock file is present on disk. /// This does not account if the files were manually modified since the last restore /// </summary> internal static bool VerifyRestoreOutput(RestoreRequest request, CacheFile cacheFile) { if (!string.IsNullOrWhiteSpace(request.LockFilePath) && !File.Exists(request.LockFilePath)) { request.Log.LogVerbose(string.Format(CultureInfo.CurrentCulture, Strings.Log_AssetsFileNotOnDisk, request.Project.Name)); return false; } if (request.ProjectStyle == ProjectStyle.PackageReference) { var targetsFilePath = BuildAssetsUtils.GetMSBuildFilePath(request.Project, BuildAssetsUtils.TargetsExtension); if (!File.Exists(targetsFilePath)) { request.Log.LogVerbose(string.Format(CultureInfo.CurrentCulture, Strings.Log_TargetsFileNotOnDisk, request.Project.Name, targetsFilePath)); return false; } var propsFilePath = BuildAssetsUtils.GetMSBuildFilePath(request.Project, BuildAssetsUtils.PropsExtension); if (!File.Exists(propsFilePath)) { request.Log.LogVerbose(string.Format(CultureInfo.CurrentCulture, Strings.Log_PropsFileNotOnDisk, request.Project.Name, propsFilePath)); return false; } if (PackagesLockFileUtilities.IsNuGetLockFileEnabled(request.Project)) { var packageLockFilePath = PackagesLockFileUtilities.GetNuGetLockFilePath(request.Project); if (!File.Exists(packageLockFilePath)) { request.Log.LogVerbose(string.Format(CultureInfo.CurrentCulture, Strings.Log_LockFileNotOnDisk, request.Project.Name, packageLockFilePath)); return false; } } } foreach (var path in cacheFile.ExpectedPackageFilePaths.AsList()) { if (!request.DependencyProviders.PackageFileCache.Sha512Exists(path)) { request.Log.LogVerbose(string.Format(CultureInfo.CurrentCulture, Strings.Log_MissingPackagesOnDisk, request.Project.Name)); return false; } } if (request.UpdatePackageLastAccessTime) { foreach (var package in cacheFile.ExpectedPackageFilePaths.AsList()) { if (!package.StartsWith(request.PackagesDirectory, StringComparison.OrdinalIgnoreCase)) { continue; } var packageDirectory = Path.GetDirectoryName(package); var metadataFile = Path.Combine(packageDirectory, PackagingCoreConstants.NupkgMetadataFileExtension); try { request.DependencyProviders.PackageFileCache.UpdateLastAccessTime(metadataFile); } catch (Exception ex) { request.Log.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1802, string.Format(CultureInfo.InvariantCulture, Strings.Error_CouldNotUpdateMetadataLastAccessTime, metadataFile, ex.Message))); } } } return true; } /// <summary> /// Generates the dgspec to be used for the no-op optimization /// This methods handles the deduping of tools /// </summary> /// <param name="request">The restore request</param> /// <returns>The noop happy dg spec</returns> /// <remarks> Could be the same instance if no changes were made to the original dgspec</remarks> internal static DependencyGraphSpec GetNoOpDgSpec(RestoreRequest request) { if (request.Project.RestoreMetadata.ProjectStyle == ProjectStyle.DotnetCliTool) { var dgSpec = request.DependencyGraphSpec.WithProjectClosure(request.DependencyGraphSpec.Restore.First()); foreach (var projectSpec in dgSpec.Projects) { // The project path where the tool is declared does not affect restore and is only used for logging and transparency. projectSpec.RestoreMetadata.ProjectPath = null; projectSpec.FilePath = null; } return dgSpec; } else { return request.DependencyGraphSpec; } } /// <summary> /// Gets the path for dgpsec.json. /// The project style that support dgpsec.json persistance are /// <see cref="ProjectStyle.PackageReference"/>, <see cref="ProjectStyle.ProjectJson"/> /// </summary> /// <param name="request"></param> /// <returns>The path for the dgspec.json. Null if not appropriate.</returns> internal static string GetPersistedDGSpecFilePath(RestoreRequest request) { if (request.ProjectStyle == ProjectStyle.ProjectJson || request.ProjectStyle == ProjectStyle.PackageReference) { var outputRoot = request.MSBuildProjectExtensionsPath ?? request.RestoreOutputPath; var projFileName = Path.GetFileName(request.Project.RestoreMetadata.ProjectPath); var dgFileName = DependencyGraphSpec.GetDGSpecFileName(projFileName); return Path.Combine(outputRoot, dgFileName); } return null; } /// <summary> /// This method will resolve the cache/lock file paths for the tool if available in the cache /// This method will set the CacheFilePath and the LockFilePath in the RestoreMetadata if a matching tool is available /// </summary> internal static void UpdateRequestBestMatchingToolPathsIfAvailable(RestoreRequest request) { if (request.ProjectStyle == ProjectStyle.DotnetCliTool) { // Resolve the lock file path if it exists var toolPathResolver = new ToolPathResolver(request.PackagesDirectory); var toolDirectory = toolPathResolver.GetBestToolDirectoryPath( ToolRestoreUtility.GetToolIdOrNullFromSpec(request.Project), request.Project.TargetFrameworks.First().Dependencies.First().LibraryRange.VersionRange, request.Project.TargetFrameworks.SingleOrDefault().FrameworkName); if (toolDirectory != null) // Only set the paths if a good enough match was found. { request.Project.RestoreMetadata.CacheFilePath = GetToolCacheFilePath(toolDirectory, ToolRestoreUtility.GetToolIdOrNullFromSpec(request.Project)); request.LockFilePath = toolPathResolver.GetLockFilePath(toolDirectory); } } } internal static List<string> GetRestoreOutput(RestoreRequest request, LockFile lockFile) { var pathResolvers = new List<VersionFolderPathResolver>(request.Project.RestoreMetadata.FallbackFolders.Count + 1) { new VersionFolderPathResolver(request.PackagesDirectory) }; foreach (string restoreMetadataFallbackFolder in request.Project.RestoreMetadata.FallbackFolders) { pathResolvers.Add(new VersionFolderPathResolver(restoreMetadataFallbackFolder)); } var packageFiles = new List<string>(lockFile.Libraries.Count + request.Project.TargetFrameworks.Sum(i => i.DownloadDependencies.Length)); foreach (var library in lockFile.Libraries) { packageFiles.AddRange(GetPackageFiles(request.DependencyProviders.PackageFileCache, library.Name, library.Version, pathResolvers)); } foreach (var targetFrameworkInformation in request.Project.TargetFrameworks) { foreach (var downloadDependency in targetFrameworkInformation.DownloadDependencies) { //TODO: https://github.com/NuGet/Home/issues/7709 - only exact versions are currently supported. The check needs to be updated when version ranges are implemented. packageFiles.AddRange(GetPackageFiles(request.DependencyProviders.PackageFileCache, downloadDependency.Name, downloadDependency.VersionRange.MinVersion, pathResolvers)); } } return packageFiles; } private static IEnumerable<string> GetPackageFiles(LocalPackageFileCache packageFileCache, string packageId, NuGetVersion version, List<VersionFolderPathResolver> resolvers) { foreach (var resolver in resolvers) { // Verify the SHA for each package var hashPath = resolver.GetHashPath(packageId, version); if (packageFileCache.Sha512Exists(hashPath)) { yield return hashPath; break; } var nupkgMetadataPath = resolver.GetNupkgMetadataPath(packageId, version); if (packageFileCache.Sha512Exists(nupkgMetadataPath)) { yield return nupkgMetadataPath; break; } } } } }