|
// 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 System.Threading;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.LibraryModel;
using NuGet.ProjectModel;
namespace NuGet.Commands
{
public class RestoreResult : IRestoreResult
{
public bool Success { get; }
/// <summary>
/// Gets the path that the lock file will be written to.
/// </summary>
public string LockFilePath { get; set; }
/// <summary>
/// Gets the resolved dependency graphs produced by the restore operation
/// </summary>
public IEnumerable<RestoreTargetGraph> RestoreGraphs { get; }
public IEnumerable<CompatibilityCheckResult> CompatibilityCheckResults { get; }
/// <summary>
/// Props and targets files to be written to disk.
/// </summary>
public IEnumerable<MSBuildOutputFile> MSBuildOutputFiles { get; }
/// <summary>
/// Restore type.
/// </summary>
public ProjectStyle ProjectStyle { get; }
/// <summary>
/// Gets the lock file that was generated during the restore or, in the case of a locked lock file,
/// was used to determine the packages to install during the restore.
/// </summary>
public virtual LockFile LockFile { get; }
/// <summary>
/// The existing lock file. This is null if no lock file was provided on the <see cref="RestoreRequest"/>.
/// </summary>
public virtual LockFile PreviousLockFile { get; }
/// <summary>
/// Restore time
/// </summary>
public TimeSpan ElapsedTime { get; }
/// <summary>
/// The log messages raised during this restore operation
/// </summary>
/// <remarks>The messages here are usually sources from the <see cref="LockFile"/> in full restores or <see cref="CacheFile"/> for no-op restores.</remarks>
public virtual IList<IAssetsLogMessage> LogMessages { get; internal set; }
/// <summary>
/// Cache File. The previous cache file for this project
/// </summary>
private CacheFile CacheFile { get; }
/// <summary>
/// Cache File path. The file path where the cache is written out
/// </summary>
protected string CacheFilePath { get; }
/// <summary>
/// New Packages lock file path
/// </summary>
private readonly string _newPackagesLockFilePath;
/// <summary>
/// NuGet lock file which is either generated or updated to lock down NuGet packages version
/// </summary>
internal PackagesLockFile _newPackagesLockFile { get; }
/// <inheritdoc cref="RestoreSummary.AuditRan"/>
internal bool AuditRan { get; init; }
/// <summary>
/// If the dg spec did not change, we can assume that we don't need to write the dg spec.
/// </summary>
internal bool DidDGHashChange { get; init; }
/// <summary>
/// If true, the dg spec file should not be written to disk.
/// </summary>
internal bool DoNotWriteDependencyGraphSpec { get; init; }
private readonly string _dependencyGraphSpecFilePath;
private readonly DependencyGraphSpec _dependencyGraphSpec;
internal readonly Lazy<bool> _isAssetsFileDirty;
internal readonly Lazy<List<MSBuildOutputFile>> _dirtyMSBuildFiles;
public RestoreResult(
bool success,
IEnumerable<RestoreTargetGraph> restoreGraphs,
IEnumerable<CompatibilityCheckResult> compatibilityCheckResults,
IEnumerable<MSBuildOutputFile> msbuildFiles,
LockFile lockFile,
LockFile previousLockFile,
string lockFilePath,
CacheFile cacheFile,
string cacheFilePath,
string packagesLockFilePath,
PackagesLockFile packagesLockFile,
string dependencyGraphSpecFilePath,
DependencyGraphSpec dependencyGraphSpec,
ProjectStyle projectStyle,
TimeSpan elapsedTime)
{
Success = success;
RestoreGraphs = restoreGraphs;
CompatibilityCheckResults = compatibilityCheckResults;
MSBuildOutputFiles = msbuildFiles;
LockFile = lockFile;
LockFilePath = lockFilePath;
PreviousLockFile = previousLockFile;
CacheFile = cacheFile;
CacheFilePath = cacheFilePath;
_newPackagesLockFilePath = packagesLockFilePath;
_newPackagesLockFile = packagesLockFile;
_dependencyGraphSpecFilePath = dependencyGraphSpecFilePath;
_dependencyGraphSpec = dependencyGraphSpec;
ProjectStyle = projectStyle;
ElapsedTime = elapsedTime;
LogMessages = lockFile?.LogMessages ?? new List<IAssetsLogMessage>();
_isAssetsFileDirty = new Lazy<bool>(() => PreviousLockFile == null
|| !PreviousLockFile.Equals(LockFile));
_dirtyMSBuildFiles = new Lazy<List<MSBuildOutputFile>>(() =>
{
return MSBuildOutputFiles.Where(e => BuildAssetsUtils.HasChanges(e.Content, e.Path, NullLogger.Instance)).ToList();
});
}
/// <summary>
/// Calculates the complete set of all packages installed by this operation
/// </summary>
/// <remarks>
/// This requires quite a bit of iterating over the graph so the result should be cached
/// </remarks>
/// <returns>A set of libraries that were installed by this operation</returns>
public virtual ISet<LibraryIdentity> GetAllInstalled()
{
return new HashSet<LibraryIdentity>(RestoreGraphs.Where(g => !g.InConflict).SelectMany(g => g.Install).Distinct().Select(m => m.Library));
}
/// <summary>
/// Calculates the complete set of all unresolved dependencies for this operation
/// </summary>
/// <remarks>
/// This requires quite a bit of iterating over the graph so the result should be cached
/// </remarks>
/// <returns>A set of dependencies that were unable to be resolved by this operation</returns>
public ISet<LibraryRange> GetAllUnresolved()
{
return new HashSet<LibraryRange>(RestoreGraphs.SelectMany(g => g.Unresolved).Distinct());
}
/// <summary>
/// Commits the lock file contained in <see cref="LockFile"/> and the MSBuild targets/props to
/// the local file system.
/// </summary>
/// <remarks>If <see cref="PreviousLockFile"/> and <see cref="LockFile"/> are identical
/// the file will not be written to disk.</remarks>
public virtual async Task CommitAsync(ILogger log, CancellationToken token)
{
if (log == null)
{
throw new ArgumentNullException(nameof(log));
}
// Write the lock file
var lockFileFormat = new LockFileFormat();
var isTool = ProjectStyle == ProjectStyle.DotnetCliTool;
// Commit the assets file to disk.
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WriteAssetsFileStart(LockFilePath);
await CommitAssetsFileAsync(
lockFileFormat,
log: log,
toolCommit: isTool,
token: token);
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WriteAssetsFileStop(LockFilePath);
//Commit the cache file to disk
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WriteCacheFileStart(CacheFilePath);
await CommitCacheFileAsync(
log: log,
toolCommit: isTool);
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WriteCacheFileStop(CacheFilePath);
// Commit the lock file to disk
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WritePackagesLockFileStart(_newPackagesLockFilePath);
await CommitLockFileAsync(
log: log,
toolCommit: isTool);
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WritePackagesLockFileStop(_newPackagesLockFilePath);
// Commit the dg spec file to disk
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WriteDgSpecFileStart(_dependencyGraphSpecFilePath);
await CommitDgSpecFileAsync(
log: log,
toolCommit: isTool);
if (CommandsEventSource.Instance.IsEnabled()) CommandsEventSource.Instance.RestoreResult_WriteDgSpecFileStop(_dependencyGraphSpecFilePath);
}
private async Task CommitAssetsFileAsync(
LockFileFormat lockFileFormat,
ILogger log,
bool toolCommit,
CancellationToken token)
{
token.ThrowIfCancellationRequested();
// Commit targets/props to disk before the assets file.
// Visual Studio typically watches the assets file for changes
// and begins a reload when that file changes.
BuildAssetsUtils.WriteFiles(_dirtyMSBuildFiles.Value, log);
if (LockFile == null || LockFilePath == null)
{
// there is no assets file to be written so just return
return;
}
// Avoid writing out the lock file if it is the same to avoid triggering an intellisense
// update on a restore with no actual changes.
if (_isAssetsFileDirty.Value)
{
if (toolCommit)
{
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.Log_ToolWritingAssetsFile,
LockFilePath));
await FileUtility.ReplaceWithLock(
(outputPath) => lockFileFormat.Write(outputPath, LockFile),
LockFilePath);
}
else
{
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.Log_WritingAssetsFile,
LockFilePath));
FileUtility.Replace(
(outputPath) => lockFileFormat.Write(outputPath, LockFile),
LockFilePath);
}
}
else
{
if (toolCommit)
{
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.Log_ToolSkippingAssetsFile,
LockFilePath));
}
else
{
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.Log_SkippingAssetsFile,
LockFilePath));
}
}
}
private async Task CommitCacheFileAsync(ILogger log, bool toolCommit)
{
if (CacheFile != null && CacheFilePath != null)
{ // This is done to preserve the old behavior
if (toolCommit)
{
log.LogVerbose(string.Format(CultureInfo.CurrentCulture,
Strings.Log_ToolWritingCacheFile,
CacheFilePath));
}
else
{
log.LogVerbose(string.Format(CultureInfo.CurrentCulture,
Strings.Log_WritingCacheFile,
CacheFilePath));
}
await FileUtility.ReplaceWithLock(
outPath => CacheFileFormat.Write(outPath, CacheFile),
CacheFilePath);
}
}
private async Task CommitLockFileAsync(ILogger log, bool toolCommit)
{
// write packages lock file if it's not tool commit
if (!toolCommit && _newPackagesLockFile != null && !string.IsNullOrEmpty(_newPackagesLockFilePath))
{
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.Log_WritingPackagesLockFile,
_newPackagesLockFilePath));
await FileUtility.ReplaceWithLock(
(outputPath) => PackagesLockFileFormat.Write(outputPath, _newPackagesLockFile),
_newPackagesLockFilePath);
}
}
private async Task CommitDgSpecFileAsync(ILogger log, bool toolCommit)
{
if (!toolCommit && !DoNotWriteDependencyGraphSpec && _dependencyGraphSpecFilePath != null && _dependencyGraphSpec != null && (DidDGHashChange || !File.Exists(_dependencyGraphSpecFilePath)))
{
log.LogVerbose($"Persisting dg to {_dependencyGraphSpecFilePath}");
await FileUtility.ReplaceWithLock(
(outputPath) => _dependencyGraphSpec.Save(outputPath),
_dependencyGraphSpecFilePath);
}
}
internal virtual IReadOnlyList<string> GetDirtyFiles()
{
List<string> dirtyFiles = null;
if (_dirtyMSBuildFiles.Value.Count > 0)
{
dirtyFiles = _dirtyMSBuildFiles.Value.Select(e => e.Path).ToList();
}
if (_isAssetsFileDirty.Value)
{
dirtyFiles ??= new List<string>(1);
dirtyFiles.Add(LockFilePath);
}
return dirtyFiles;
}
}
}
|