Read the existing lock file for the tool

Don't rewrite the same tool lock file (just like existing project lock file logic)
Do not observe the "locked" property in tool restore
This commit is contained in:
Joel Verhagen committed 2016-03-22 15:53:49 -07:00
1 parent 6130dcc376
commit 059e4e94f0
12 files changed
+940 -127

No files matched your search

@@ -0,0 +1,34 @@
// 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 NuGet.ProjectModel;
namespace NuGet.Commands
{
public interface IRestoreResult
{
bool Success { get; }
/// <summary>
/// Gets the path that the lock file will be written to.
/// </summary>
string LockFilePath { get; }
/// <summary>
/// Gets a boolean indicating if the lock file will be re-written on <see cref="Commit"/>
/// because the file needs to be re-locked.
/// </summary>
bool RelockFile { 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>
LockFile LockFile { get; }
/// <summary>
/// The existing lock file. This is null if no lock file was provided on the <see cref="RestoreRequest"/>.
/// </summary>
LockFile PreviousLockFile { get; }
}
}
@@ -316,10 +316,14 @@ namespace NuGet.Commands
foreach (var dependency in dependencies)
{
// Ignore floating or version-less (project) dependencies
if (dependency.LibraryRange.VersionRange != null && !dependency.LibraryRange.VersionRange.IsFloating)
// Avoid warnings for non-packages
if (dependency.LibraryRange.TypeConstraintAllows(LibraryDependencyTarget.Package)
&& dependency.LibraryRange.VersionRange != null && !dependency.LibraryRange.VersionRange.IsFloating)
{
var match = result.Flattened.FirstOrDefault(g => g.Key.Name.Equals(dependency.LibraryRange.Name));
if (match != null && match.Key.Version > dependency.LibraryRange.VersionRange.MinVersion)
if (match != null
&& LibraryTypes.Package == match.Key.Type
&& match.Key.Version > dependency.LibraryRange.VersionRange.MinVersion)
{
_logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Strings.Log_DependencyBumpedUp,
dependency.LibraryRange.Name,
@@ -8,7 +8,6 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
@@ -72,16 +71,7 @@ namespace NuGet.Commands
Path.Combine(_request.Project.BaseDirectory, LockFileFormat.LockFileName) :
_request.LockFilePath;
bool relockFile = false;
if (_request.ExistingLockFile != null
&& _request.ExistingLockFile.IsLocked
&& !_request.ExistingLockFile.IsValidForPackageSpec(_request.Project, _request.LockFileVersion))
{
// The lock file was locked, but the project.json is out of date
relockFile = true;
_request.ExistingLockFile.IsLocked = false;
_logger.LogMinimal(Strings.Log_LockFileOutOfDate);
}
var relockFile = ShouldRelockFile(_request.ExistingLockFile, _request.Project);
var contextForProject = CreateRemoteWalkContext(_request);
@@ -95,27 +85,14 @@ namespace NuGet.Commands
toolRestoreResults = await ExecuteToolRestoresAsync(localRepository, token);
}
// Build the lock file
LockFile lockFile;
if (_request.ExistingLockFile != null && _request.ExistingLockFile.IsLocked)
{
// No lock file to write!
lockFile = _request.ExistingLockFile;
}
else
{
lockFile = new LockFileBuilder(_request.LockFileVersion, _logger, _includeFlagGraphs)
.CreateLockFile(
_request.ExistingLockFile,
_request.Project,
graphs,
localRepository,
contextForProject,
toolRestoreResults);
// If the lock file was locked originally but we are re-locking it, well... re-lock it :)
lockFile.IsLocked = relockFile;
}
var lockFile = BuildLockFile(
_request.ExistingLockFile,
_request.Project,
graphs,
localRepository,
contextForProject,
toolRestoreResults,
relockFile);
if(!ValidateRestoreGraphs(graphs, _logger))
{
@@ -167,6 +144,55 @@ namespace NuGet.Commands
toolRestoreResults);
}
private LockFile BuildLockFile(
LockFile existingLockFile,
PackageSpec project,
IEnumerable<RestoreTargetGraph> graphs,
NuGetv3LocalRepository localRepository,
RemoteWalkContext contextForProject,
IEnumerable<ToolRestoreResult> toolRestoreResults,
bool relockFile)
{
// Build the lock file
LockFile lockFile;
if (existingLockFile != null && existingLockFile.IsLocked)
{
// No lock file to write!
lockFile = existingLockFile;
}
else
{
lockFile = new LockFileBuilder(_request.LockFileVersion, _logger, _includeFlagGraphs)
.CreateLockFile(
existingLockFile,
project,
graphs,
localRepository,
contextForProject,
toolRestoreResults);
// If the lock file was locked originally but we are re-locking it, well... re-lock it :)
lockFile.IsLocked = relockFile;
}
return lockFile;
}
private bool ShouldRelockFile(LockFile existingLockFile, PackageSpec project)
{
bool relockFile = false;
if (existingLockFile != null
&& existingLockFile.IsLocked
&& !existingLockFile.IsValidForPackageSpec(project, _request.LockFileVersion))
{
// The lock file was locked, but the project.json is out of date
relockFile = true;
existingLockFile.IsLocked = false;
_logger.LogMinimal(Strings.Log_LockFileOutOfDate);
}
return relockFile;
}
private static bool ValidateRestoreGraphs(IEnumerable<RestoreTargetGraph> graphs, ILogger logger)
{
foreach (var g in graphs)
@@ -216,7 +242,6 @@ namespace NuGet.Commands
CancellationToken token)
{
var toolPathResolver = new ToolPathResolver(_request.PackagesDirectory);
var lockFileBuilder = new LockFileBuilder(_request.LockFileVersion, _logger, _includeFlagGraphs);
var results = new List<ToolRestoreResult>();
foreach (var tool in _request.Project.Tools)
@@ -251,6 +276,39 @@ namespace NuGet.Commands
}
};
// Try to find the existing lock file. Since the existing lock file is pathed under
// a folder that includes the resolved tool's version, this is a bit of a chicken
// and egg problem. That is, we need to run the restore operation in order to resolve
// a tool version, but we need the tool version to find the existing project.lock.json
// file which is required before executing the restore! Fortunately, this is solved by
// looking at the tool's consuming project's lock file to see if the tool has been
// restored before.
LockFile existingToolLockFile = null;
if (_request.ExistingLockFile != null)
{
var existingTarget = _request
.ExistingLockFile
.Tools
.Where(t => t.RuntimeIdentifier == null)
.Where(t => t.TargetFramework.Equals(LockFile.ToolFramework))
.FirstOrDefault();
var existingLibrary = existingTarget?.Libraries
.Where(l => StringComparer.OrdinalIgnoreCase.Equals(l.Name, tool.LibraryRange.Name))
.Where(l => tool.LibraryRange.VersionRange.Satisfies(l.Version))
.FirstOrDefault();
if (existingLibrary != null)
{
var existingLockFilePath = toolPathResolver.GetLockFilePath(
existingLibrary.Name,
existingLibrary.Version,
existingTarget.TargetFramework);
existingToolLockFile = LockFileUtilities.GetLockFile(existingLockFilePath, _logger);
}
}
// Execute the restore.
var toolSuccess = true; // success for this individual tool restore
var runtimeIds = new HashSet<string>();
@@ -261,7 +319,7 @@ namespace NuGet.Commands
var projectRestoreRequest = new ProjectRestoreRequest(
_request,
toolPackageSpec,
null,
existingToolLockFile,
new Dictionary<NuGetFramework, RuntimeGraph>(),
_runtimeGraphCacheByPackage);
var projectRestoreCommand = new ProjectRestoreCommand(_logger, projectRestoreRequest);
@@ -283,24 +341,25 @@ namespace NuGet.Commands
}
// Create the lock file (in memory).
var lockFile = lockFileBuilder.CreateLockFile(
previousLockFile: null,
project: toolPackageSpec,
targetGraphs: graphs,
repository: localRepository,
context: contextForTool,
toolRestoreResults: Enumerable.Empty<ToolRestoreResult>());
var toolLockFile = BuildLockFile(
existingToolLockFile,
toolPackageSpec,
graphs,
localRepository,
contextForTool,
Enumerable.Empty<ToolRestoreResult>(),
false);
// Build the path based off of the resolved tool. For now, we assume there is only
// ever one target.
var target = lockFile.Targets.Single();
var target = toolLockFile.Targets.Single();
var fileTargetLibrary = target
.Libraries
.FirstOrDefault(l => StringComparer.OrdinalIgnoreCase.Equals(tool.LibraryRange.Name, l.Name));
string lockFilePath = null;
string toolLockFilePath = null;
if (fileTargetLibrary != null)
{
lockFilePath = toolPathResolver.GetLockFilePath(
toolLockFilePath = toolPathResolver.GetLockFilePath(
fileTargetLibrary.Name,
fileTargetLibrary.Version,
target.TargetFramework);
@@ -318,8 +377,9 @@ namespace NuGet.Commands
toolSuccess,
target,
fileTargetLibrary,
lockFilePath,
lockFile));
toolLockFilePath,
toolLockFile,
existingToolLockFile));
}
return results;
@@ -651,6 +711,10 @@ namespace NuGet.Commands
// Null out all target types, these did not exist in v1
library.Type = null;
}
// Remove tools
lockFile.Tools = null;
lockFile.ProjectFileToolGroups = null;
}
}
}
@@ -13,7 +13,7 @@ using NuGet.ProjectModel;
namespace NuGet.Commands
{
public class RestoreResult
public class RestoreResult : IRestoreResult
{
public bool Success { get; }
@@ -111,54 +111,102 @@ namespace NuGet.Commands
/// </summary>
/// <remarks>If <see cref="PreviousLockFile"/> and <see cref="LockFile"/> are identical
/// the file will not be written to disk.</remarks>
/// <param name="log">The logger.</param>
/// <param name="forceWrite">Write out the lock file even if no changes exist.</param>
/// <param name="token">The cancellation token.</param>
public async Task CommitAsync(ILogger log, bool forceWrite, CancellationToken token)
{
// Write the lock file
var lockFileFormat = new LockFileFormat();
// Don't write the lock file if it is Locked AND we're not re-locking the file
if (!LockFile.IsLocked || RelockFile)
{
// 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 (forceWrite
|| PreviousLockFile == null
|| !PreviousLockFile.Equals(LockFile))
{
log.LogMinimal($"Writing lock file to disk. Path: {LockFilePath}");
await CommitAsync(
lockFileFormat,
result: this,
log: log,
forceWrite: forceWrite,
toolCommit: false,
token: token);
lockFileFormat.Write(LockFilePath, LockFile);
}
else
{
log.LogMinimal($"Lock file has not changed. Skipping lock file write. Path: {LockFilePath}");
}
}
// Always write the tool lock files
foreach (var toolRestoreResult in ToolRestoreResults)
{
if (toolRestoreResult.LockFilePath != null)
if (toolRestoreResult.LockFilePath != null && toolRestoreResult.LockFile != null)
{
log.LogDebug($"Writing tool lock file to disk. Path: {toolRestoreResult.LockFilePath}");
await ConcurrencyUtilities.ExecuteWithFileLockedAsync(
toolRestoreResult.LockFilePath,
lockedToken =>
{
var lockFileDirectory = Path.GetDirectoryName(toolRestoreResult.LockFilePath);
Directory.CreateDirectory(lockFileDirectory);
lockFileFormat.Write(toolRestoreResult.LockFilePath, toolRestoreResult.LockFile);
return Task.FromResult((object)null);
},
token);
await CommitAsync(
lockFileFormat,
result: toolRestoreResult,
log: log,
forceWrite: forceWrite,
toolCommit: true,
token: token);
}
}
MSBuild.Commit(log);
}
private static async Task CommitAsync(
LockFileFormat lockFileFormat,
IRestoreResult result,
ILogger log,
bool forceWrite,
bool toolCommit,
CancellationToken token)
{
// Don't write the lock file if it is Locked AND we're not re-locking the file
if (!result.LockFile.IsLocked || result.RelockFile || toolCommit)
{
// 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 (forceWrite
|| result.PreviousLockFile == null
|| !result.PreviousLockFile.Equals(result.LockFile))
{
if (toolCommit)
{
log.LogDebug($"Writing tool lock file to disk. Path: {result.LockFilePath}");
await ConcurrencyUtilities.ExecuteWithFileLockedAsync(
result.LockFilePath,
lockedToken =>
{
var lockFileDirectory = Path.GetDirectoryName(result.LockFilePath);
Directory.CreateDirectory(lockFileDirectory);
lockFileFormat.Write(result.LockFilePath, result.LockFile);
return Task.FromResult(0);
},
token);
}
else
{
log.LogMinimal($"Writing lock file to disk. Path: {result.LockFilePath}");
lockFileFormat.Write(result.LockFilePath, result.LockFile);
}
}
else
{
if (toolCommit)
{
log.LogDebug($"Tool lock file has not changed. Skipping lock file write. Path: {result.LockFilePath}");
}
else
{
log.LogMinimal($"Lock file has not changed. Skipping lock file write. Path: {result.LockFilePath}");
}
}
}
}
private static void WriteLockFile(
LockFileFormat lockFileFormat,
IRestoreResult result,
bool createDirectory)
{
if (createDirectory)
{
}
lockFileFormat.Write(result.LockFilePath, result.LockFile);
}
}
}
@@ -5,7 +5,7 @@ using NuGet.ProjectModel;
namespace NuGet.Commands
{
public class ToolRestoreResult
public class ToolRestoreResult : IRestoreResult
{
public ToolRestoreResult(
string toolName,
@@ -13,7 +13,8 @@ namespace NuGet.Commands
LockFileTarget lockFileTarget,
LockFileTargetLibrary fileTargetLibrary,
string lockFilePath,
LockFile lockFile)
LockFile lockFile,
LockFile previousLockFile)
{
ToolName = toolName;
Success = success;
@@ -21,6 +22,10 @@ namespace NuGet.Commands
FileTargetLibrary = fileTargetLibrary;
LockFilePath = lockFilePath;
LockFile = lockFile;
PreviousLockFile = previousLockFile;
// "locked" property is not supported on tools
RelockFile = false;
}
public string ToolName { get; }
@@ -29,5 +34,7 @@ namespace NuGet.Commands
public LockFileTargetLibrary FileTargetLibrary { get; }
public string LockFilePath { get; }
public LockFile LockFile { get; }
public LockFile PreviousLockFile { get; }
public bool RelockFile { get; }
}
}
+31 -13
View File
@@ -72,9 +72,11 @@ namespace NuGet.ProjectModel
}
else
{
var framework = actualTargetFrameworks
.FirstOrDefault(f =>
string.Equals(f.FrameworkName.ToString(), @group.FrameworkName, StringComparison.OrdinalIgnoreCase));
var framework = actualTargetFrameworks.FirstOrDefault(f => string.Equals(
f.FrameworkName.DotNetFrameworkName,
@group.FrameworkName,
StringComparison.OrdinalIgnoreCase));
if (framework == null)
{
return false;
@@ -166,7 +168,11 @@ namespace NuGet.ProjectModel
group => group.FrameworkName, StringComparer.OrdinalIgnoreCase))
&& Libraries.OrderBy(library => library.Name, StringComparer.OrdinalIgnoreCase)
.SequenceEqual(other.Libraries.OrderBy(library => library.Name, StringComparer.OrdinalIgnoreCase))
&& Targets.OrderBy(target => target.Name).SequenceEqual(other.Targets.OrderBy(target => target.Name));
&& Targets.OrderBy(target => target.Name).SequenceEqual(other.Targets.OrderBy(target => target.Name))
&& ProjectFileToolGroups.OrderBy(group => group.FrameworkName, StringComparer.OrdinalIgnoreCase)
.SequenceEqual(other.ProjectFileToolGroups.OrderBy(
group => group.FrameworkName, StringComparer.OrdinalIgnoreCase))
&& Tools.OrderBy(target => target.Name).SequenceEqual(other.Tools.OrderBy(target => target.Name));
}
public override bool Equals(object obj)
@@ -181,23 +187,35 @@ namespace NuGet.ProjectModel
combiner.AddObject(IsLocked);
combiner.AddObject(Version);
foreach (var item in ProjectFileDependencyGroups.OrderBy(
group => group.FrameworkName, StringComparer.OrdinalIgnoreCase))
{
combiner.AddObject(item);
}
HashProjectFileDependencyGroups(combiner, ProjectFileDependencyGroups);
foreach (var item in Libraries.OrderBy(library => library.Name, StringComparer.OrdinalIgnoreCase))
{
combiner.AddObject(item);
}
foreach (var item in Targets.OrderBy(target => target.Name, StringComparer.OrdinalIgnoreCase))
{
combiner.AddObject(item);
}
HashLockFileTargets(combiner, Targets);
HashProjectFileDependencyGroups(combiner, ProjectFileToolGroups);
HashLockFileTargets(combiner, Tools);
return combiner.CombinedHash;
}
private static void HashLockFileTargets(HashCodeCombiner combiner, IList<LockFileTarget> targets)
{
foreach (var item in targets.OrderBy(target => target.Name, StringComparer.OrdinalIgnoreCase))
{
combiner.AddObject(item);
}
}
private static void HashProjectFileDependencyGroups(HashCodeCombiner combiner, IList<ProjectFileDependencyGroup> groups)
{
foreach (var item in groups.OrderBy(
group => @group.FrameworkName, StringComparer.OrdinalIgnoreCase))
{
combiner.AddObject(item);
}
}
}
}
@@ -183,8 +183,17 @@ namespace NuGet.ProjectModel
json[TargetsProperty] = WriteObject(lockFile.Targets, WriteTarget);
json[LibrariesProperty] = WriteObject(lockFile.Libraries, WriteLibrary);
json[ProjectFileDependencyGroupsProperty] = WriteObject(lockFile.ProjectFileDependencyGroups, WriteProjectFileDependencyGroup);
json[ToolsProperty] = WriteObject(lockFile.Tools, WriteTarget);
json[ProjectFileToolGroupsProperty] = WriteObject(lockFile.ProjectFileToolGroups, WriteProjectFileDependencyGroup);
if (lockFile.Tools != null)
{
json[ToolsProperty] = WriteObject(lockFile.Tools, WriteTarget);
}
if (lockFile.ProjectFileToolGroups != null)
{
json[ProjectFileToolGroupsProperty] = WriteObject(lockFile.ProjectFileToolGroups, WriteProjectFileDependencyGroup);
}
return json;
}
@@ -15379,5 +15379,7 @@
"Microsoft.NETCore.UniversalWindowsPlatform >= 5.0.0"
],
"UAP,Version=v10.0": []
}
},
"tools": {},
"projectFileToolGroups": {}
}
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.Logging;
using NuGet.ProjectModel;
using NuGet.Test.Utility;
using Xunit;
@@ -247,6 +248,124 @@ namespace NuGet.Commands.Test
}
}
[Fact]
public async Task RestoreCommand_DoesNotRedoRestoreToolsWithValidLockFile()
{
// Arrange
using (var workingDir = TestFileSystemUtility.CreateRandomTestFolder())
{
var tc = new ToolTestContext(workingDir)
{
ProjectJson = @"
{
""frameworks"": {
""net45"": { }
},
""tools"": {
""packageA"": ""*""
}
}",
};
var packageA = new SimpleTestPackageContext("packageA");
packageA.AddFile("lib/netstandard1.3/a.dll");
SimpleTestPackageUtility.CreatePackages(tc.PackageSource.FullName, packageA);
tc.Initialize();
// the first restore
await (await tc.Command.ExecuteAsync()).CommitAsync(tc.Logger, CancellationToken.None);
// reset
tc.Logger = new TestLogger();
tc.Request.Log = tc.Logger;
tc.Initialize();
tc.Request.ExistingLockFile = LockFileUtilities.GetLockFile(tc.Request.LockFilePath, tc.Logger);
// Act
var result = await tc.Command.ExecuteAsync();
await result.CommitAsync(tc.Logger, CancellationToken.None);
// Assert
Assert.True(
result.Success,
"The command did not succeed. Error messages: "
+ Environment.NewLine + tc.Logger.ShowErrors());
Assert.Equal(1, result.ToolRestoreResults.Count());
Assert.Contains(
$"Lock file has not changed. Skipping lock file write. Path: {result.LockFilePath}",
tc.Logger.Messages);
var toolResult = result.ToolRestoreResults.First();
Assert.Contains(
$"Tool lock file has not changed. Skipping lock file write. Path: {toolResult.LockFilePath}",
tc.Logger.Messages);
}
}
[Fact]
public async Task RestoreCommand_ObservesLockedFilesOnlyInProject()
{
// Arrange
using (var workingDir = TestFileSystemUtility.CreateRandomTestFolder())
{
var tc = new ToolTestContext(workingDir)
{
ProjectJson = @"
{
""frameworks"": {
""net45"": { }
},
""tools"": {
""packageA"": ""*""
}
}",
};
var packageA = new SimpleTestPackageContext("packageA");
packageA.AddFile("lib/netstandard1.3/a.dll");
SimpleTestPackageUtility.CreatePackages(tc.PackageSource.FullName, packageA);
tc.Initialize();
// the first restore
var initialResult = await tc.Command.ExecuteAsync();
await initialResult.CommitAsync(tc.Logger, CancellationToken.None);
tc.SetIsLocked(initialResult.LockFilePath, true, tc.Logger);
tc.SetIsLocked(initialResult.ToolRestoreResults.First().LockFilePath, true, tc.Logger);
// reset
tc.Logger = new TestLogger();
tc.Request.Log = tc.Logger;
tc.Initialize();
tc.Request.ExistingLockFile = LockFileUtilities.GetLockFile(tc.Request.LockFilePath, tc.Logger);
// Act
var result = await tc.Command.ExecuteAsync();
await result.CommitAsync(tc.Logger, CancellationToken.None);
// Assert
Assert.True(
result.Success,
"The command did not succeed. Error messages: "
+ Environment.NewLine + tc.Logger.ShowErrors());
Assert.Equal(1, result.ToolRestoreResults.Count());
// Since the files are locked and valid, the commit is completely skipped.
Assert.DoesNotContain(result.LockFilePath, tc.Logger.ShowMessages());
// Locking a tool lock file is not supported.
var toolResult = result.ToolRestoreResults.First();
Assert.Contains(
$"Tool lock file has not changed. Skipping lock file write. Path: {toolResult.LockFilePath}",
tc.Logger.Messages);
}
}
[Fact]
public async Task RestoreCommand_FailsCommandWhenToolRestoreFails()
{
@@ -379,7 +498,7 @@ namespace NuGet.Commands.Test
}
public DirectoryInfo PackageSource { get; }
public TestLogger Logger { get; }
public TestLogger Logger { get; set; }
public string ProjectJson { private get; set; }
public RestoreRequest Request { get; set; }
public RestoreCommand Command { get; private set; }
@@ -399,6 +518,13 @@ namespace NuGet.Commands.Test
Request.LockFilePath = Path.Combine(Project.FullName, "project.lock.json");
Command = new RestoreCommand(Request);
}
public void SetIsLocked(string path, bool isLocked, ILogger logger)
{
var lockFile = LockFileUtilities.GetLockFile(path, logger);
lockFile.IsLocked = isLocked;
new LockFileFormat().Write(path, lockFile);
}
}
}
}
@@ -78,13 +78,16 @@ namespace NuGet.Commands.Test
// Arrange
using (var td = TestFileSystemUtility.CreateRandomTestFolder())
{
var path = Path.Combine(td, "project.lock.json");
var path = Path.Combine(td, ".tools", "project.lock.json");
var logger = new TestLogger();
var toolResult = new ToolRestoreResult(
toolName: null,
success: true,
restoreGraphs: null,
lockFileTarget: null,
fileTargetLibrary: null,
lockFilePath: path,
lockFile: new LockFile());
lockFile: new LockFile(),
previousLockFile: null);
var result = new RestoreResult(
success: true,
restoreGraphs: null,
@@ -104,7 +107,44 @@ namespace NuGet.Commands.Test
logger.DebugMessages);
Assert.True(File.Exists(path), $"The tool lock file should have been written: {path}");
Assert.Equal(1, logger.DebugMessages.Count);
Assert.Equal(1, logger.MinimalMessages.Count);
}
}
[Fact]
public async Task RestoreResult_WritesToolSkipCommitToDebug()
{
// Arrange
using (var td = TestFileSystemUtility.CreateRandomTestFolder())
{
var path = Path.Combine(td, "tools", "project.lock.json");
var logger = new TestLogger();
var toolResult = new ToolRestoreResult(
toolName: null,
success: true,
lockFileTarget: null,
fileTargetLibrary: null,
lockFilePath: path,
lockFile: new LockFile(),
previousLockFile: new LockFile()); // same lock file
var result = new RestoreResult(
success: true,
restoreGraphs: null,
compatibilityCheckResults: null,
lockFile: new LockFile(),
previousLockFile: new LockFile(), // same lock file
lockFilePath: null,
msbuild: new MSBuildRestoreResult("project", td, true),
toolRestoreResults: new[] { toolResult });
// Act
await result.CommitAsync(logger, CancellationToken.None);
// Assert
Assert.Contains(
$"Tool lock file has not changed. Skipping lock file write. Path: {path}",
logger.DebugMessages);
Assert.False(File.Exists(path), $"The tool lock file should not have been written: {path}");
Assert.Equal(1, logger.DebugMessages.Count);
}
}
}
@@ -12,10 +12,9 @@ namespace NuGet.ProjectModel.Test
public class LockFileFormatTests
{
[Fact]
public void ReadBasicLockFile()
public void LockFileFormat_ReadsLockFileWithNoTools()
{
const string lockFileContent = @"
{
string lockFileContent = @"{
""locked"": true,
""version"": 1,
""targets"": {
@@ -90,44 +89,94 @@ namespace NuGet.ProjectModel.Test
var netPlatDepGroup = lockFile.ProjectFileDependencyGroups.Last();
Assert.Equal(NuGetFramework.Parse("dotnet").DotNetFrameworkName, netPlatDepGroup.FrameworkName);
Assert.Empty(netPlatDepGroup.Dependencies);
Assert.Equal(0, lockFile.ProjectFileToolGroups.Count);
Assert.Equal(0, lockFile.Tools.Count);
}
[Fact]
public void WriteBasicLockFile()
public void LockFileFormat_ReadsLockFileWithTools()
{
string lockFileContent = @"{
""tools"": {
"".NETStandard,Version=v1.2"": {
""System.Runtime/4.0.20-beta-22927"": {
""dependencies"": {
""Frob"": ""[4.0.20, )""
},
""compile"": {
""ref/dotnet/System.Runtime.dll"": {}
}
}
}
},
""projectFileToolGroups"": {
"".NETFramework,Version=v4.5.1"": [],
"".NETStandard,Version=v1.5"": [
""System.Runtime [4.0.10-beta-*, )""
]
}
}";
var lockFileFormat = new LockFileFormat();
var lockFile = lockFileFormat.Parse(lockFileContent, "In Memory");
var tool = lockFile.Tools.Single();
Assert.Equal(FrameworkConstants.CommonFrameworks.NetStandard12, tool.TargetFramework);
var runtimeTargetLibrary = tool.Libraries.Single();
Assert.Equal("System.Runtime", runtimeTargetLibrary.Name);
Assert.Equal(NuGetVersion.Parse("4.0.20-beta-22927"), runtimeTargetLibrary.Version);
Assert.Equal(0, runtimeTargetLibrary.NativeLibraries.Count);
Assert.Equal(0, runtimeTargetLibrary.ResourceAssemblies.Count);
Assert.Equal(0, runtimeTargetLibrary.FrameworkAssemblies.Count);
Assert.Equal(0, runtimeTargetLibrary.RuntimeAssemblies.Count);
Assert.Equal("ref/dotnet/System.Runtime.dll", runtimeTargetLibrary.CompileTimeAssemblies.Single().Path);
var net451Group = lockFile.ProjectFileToolGroups.First();
Assert.Equal(FrameworkConstants.CommonFrameworks.Net451, NuGetFramework.Parse(net451Group.FrameworkName));
var netStandardGroup = lockFile.ProjectFileToolGroups.Last();
Assert.Equal(FrameworkConstants.CommonFrameworks.NetStandard15, NuGetFramework.Parse(netStandardGroup.FrameworkName));
Assert.Equal("System.Runtime [4.0.10-beta-*, )", netStandardGroup.Dependencies.Single());
}
[Fact]
public void LockFileFormat_WritesLockFileWithNoTools()
{
// Arrange
const string lockFileContent = @"{
string lockFileContent = @"{
""locked"": true,
""version"": 2,
""targets"": {
"".NETPlatform,Version=v5.0"": {
""System.Runtime/4.0.20-beta-22927"": {
""type"": ""package"",
""dependencies"": {
""Frob"": ""4.0.20""
},
"".NETPlatform,Version=v5.0"": {
""System.Runtime/4.0.20-beta-22927"": {
""type"": ""package"",
""dependencies"": {
""Frob"": ""4.0.20""
},
""compile"": {
""ref/dotnet/System.Runtime.dll"": {
}
}
}
}
},
""ref/dotnet/System.Runtime.dll"": {}
}
}
}
},
""libraries"": {
""System.Runtime/4.0.20-beta-22927"": {
""sha512"": ""sup3rs3cur3"",
""System.Runtime/4.0.20-beta-22927"": {
""sha512"": ""sup3rs3cur3"",
""type"": ""package"",
""files"": [
""System.Runtime.nuspec""
]
}
},
},
""projectFileDependencyGroups"": {
"""": [
""System.Runtime [4.0.10-beta-*, )""
],
"".NETPlatform,Version=v5.0"": []
}
},
""tools"": {},
""projectFileToolGroups"": {}
}";
var lockFile = new LockFile();
lockFile.IsLocked = true;
@@ -172,5 +221,69 @@ namespace NuGet.ProjectModel.Test
// Assert
Assert.Equal(expected.ToString(), output.ToString());
}
[Fact]
public void LockFileFormat_WritesLockFileWithTools()
{
// Arrange
string lockFileContent = @"{
""locked"": true,
""version"": 2,
""targets"": {},
""libraries"": {},
""projectFileDependencyGroups"": {},
""tools"": {
"".NETPlatform,Version=v5.0"": {
""System.Runtime/4.0.20-beta-22927"": {
""type"": ""package"",
""dependencies"": {
""Frob"": ""4.0.20""
},
""compile"": {
""ref/dotnet/System.Runtime.dll"": {}
}
}
}
},
""projectFileToolGroups"": {
"""": [
""System.Runtime [4.0.10-beta-*, )""
],
"".NETPlatform,Version=v5.0"": []
}
}";
var lockFile = new LockFile();
lockFile.IsLocked = true;
lockFile.Version = 2;
var target = new LockFileTarget()
{
TargetFramework = FrameworkConstants.CommonFrameworks.DotNet
};
var targetLib = new LockFileTargetLibrary()
{
Name = "System.Runtime",
Version = NuGetVersion.Parse("4.0.20-beta-22927"),
Type = LibraryTypes.Package
};
targetLib.Dependencies.Add(new NuGet.Packaging.Core.PackageDependency("Frob",
new VersionRange(NuGetVersion.Parse("4.0.20"))));
targetLib.CompileTimeAssemblies.Add(new LockFileItem("ref/dotnet/System.Runtime.dll"));
target.Libraries.Add(targetLib);
lockFile.Tools.Add(target);
lockFile.ProjectFileToolGroups.Add(
new ProjectFileDependencyGroup("", new string[] { "System.Runtime [4.0.10-beta-*, )" }));
lockFile.ProjectFileToolGroups.Add(
new ProjectFileDependencyGroup(FrameworkConstants.CommonFrameworks.DotNet.DotNetFrameworkName, new string[0]));
// Act
var lockFileFormat = new LockFileFormat();
var output = JObject.Parse(lockFileFormat.Render(lockFile));
var expected = JObject.Parse(lockFileContent);
// Assert
Assert.Equal(expected.ToString(), output.ToString());
}
}
}
@@ -0,0 +1,348 @@
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.Versioning;
using Xunit;
namespace NuGet.ProjectModel.Test
{
public class LockFileTests
{
[Fact]
public void LockFile_ComparesEqualTools()
{
// Arrange
var lockFileA = new LockFile
{
Tools =
{
new LockFileTarget
{
TargetFramework = FrameworkConstants.CommonFrameworks.NetStandard12,
Libraries = { new LockFileTargetLibrary { Name = "SomeLibrary" } }
}
}
};
// same thing
var lockFileB = new LockFile
{
Tools =
{
new LockFileTarget
{
TargetFramework = FrameworkConstants.CommonFrameworks.NetStandard12,
Libraries = { new LockFileTargetLibrary { Name = "SomeLibrary" } }
}
}
};
// Act & Assert
Assert.True(lockFileA.Equals(lockFileB), "The two lock files should be equal.");
}
[Fact]
public void LockFile_ComparesDifferentTools()
{
// Arrange
var lockFileA = new LockFile
{
Tools =
{
new LockFileTarget
{
TargetFramework = FrameworkConstants.CommonFrameworks.NetStandard12,
Libraries = { new LockFileTargetLibrary { Name = "SomeLibrary" } }
}
}
};
// different thing
var lockFileB = new LockFile
{
Tools =
{
new LockFileTarget
{
TargetFramework = FrameworkConstants.CommonFrameworks.NetStandard12,
Libraries = { new LockFileTargetLibrary { Name = "SomeLibrary" } }
},
new LockFileTarget
{
TargetFramework = FrameworkConstants.CommonFrameworks.NetStandard12,
Libraries = { new LockFileTargetLibrary { Name = "OtherLibrary" } }
}
}
};
// Act & Assert
Assert.False(lockFileA.Equals(lockFileB), "The two lock files should not be equal.");
}
[Fact]
public void LockFile_ComparesEqualProjectFileToolGroups()
{
// Arrange
var lockFileA = new LockFile
{
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup("FrameworkA", new [] { "DependencyA" })
}
};
// same thing
var lockFileB = new LockFile
{
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup("FrameworkA", new [] { "DependencyA" })
}
};
// Act & Assert
Assert.True(lockFileA.Equals(lockFileB), "The two lock files should be equal.");
}
[Fact]
public void LockFile_ComparesDifferentProjectFileToolGroups()
{
// Arrange
var lockFileA = new LockFile
{
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup("FrameworkA", new [] { "DependencyA" })
}
};
// different thing
var lockFileB = new LockFile
{
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup("FrameworkA", new [] { "DependencyA" }),
new ProjectFileDependencyGroup("FrameworkB", new [] { "DependencyB" })
}
};
// Act & Assert
Assert.False(lockFileA.Equals(lockFileB), "The two lock files should not be equal.");
}
[Fact]
public void LockFile_IsValidForPackageSpec_DifferentToolVersions()
{
// Arrange
var lockFile = new LockFile
{
ProjectFileDependencyGroups =
{
new ProjectFileDependencyGroup("", new string[0])
},
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup(LockFile.ToolFramework.ToString(), new [] { "DependencyA >= 2.0.0" })
}
};
var packageSpec = new PackageSpec(new JObject())
{
Dependencies = new List<LibraryDependency>(),
Tools = new List<ToolDependency>
{
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyA",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("1.0.0")
}
}
}
};
// Act & Assert
Assert.False(lockFile.IsValidForPackageSpec(packageSpec));
}
[Fact]
public void LockFile_IsValidForPackageSpec_MissingTools()
{
// Arrange
var lockFile = new LockFile
{
ProjectFileDependencyGroups =
{
new ProjectFileDependencyGroup("", new string[0])
},
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup(LockFile.ToolFramework.ToString(), new [] { "DependencyA >= 2.0.0" })
}
};
var packageSpec = new PackageSpec(new JObject())
{
Dependencies = new List<LibraryDependency>(),
Tools = new List<ToolDependency>
{
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyA",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("1.0.0")
}
},
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyB",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("2.0.0")
}
}
}
};
// Act & Assert
Assert.False(lockFile.IsValidForPackageSpec(packageSpec));
}
[Fact]
public void LockFile_IsValidForPackageSpec_ExtraTools()
{
// Arrange
var lockFile = new LockFile
{
ProjectFileDependencyGroups =
{
new ProjectFileDependencyGroup("", new string[0])
},
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup(LockFile.ToolFramework.ToString(), new []
{
"DependencyA >= 1.0.0",
"DependencyB >= 2.0.0",
})
}
};
var packageSpec = new PackageSpec(new JObject())
{
Dependencies = new List<LibraryDependency>(),
Tools = new List<ToolDependency>
{
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyA",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("1.0.0")
}
}
}
};
// Act & Assert
Assert.False(lockFile.IsValidForPackageSpec(packageSpec));
}
[Fact]
public void LockFile_IsValidForPackageSpec_InvalidToolFramework()
{
// Arrange
var lockFile = new LockFile
{
ProjectFileDependencyGroups =
{
new ProjectFileDependencyGroup("", new string[0])
},
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup("FrameworkA", new []
{
"DependencyA >= 1.0.0"
})
}
};
var packageSpec = new PackageSpec(new JObject())
{
Dependencies = new List<LibraryDependency>(),
Tools = new List<ToolDependency>
{
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyA",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("1.0.0")
}
}
}
};
// Act & Assert
Assert.False(lockFile.IsValidForPackageSpec(packageSpec));
}
[Fact]
public void LockFile_IsValidForPackageSpec_MatchingTools()
{
// Arrange
var lockFile = new LockFile
{
ProjectFileDependencyGroups =
{
new ProjectFileDependencyGroup("", new string[0])
},
ProjectFileToolGroups =
{
new ProjectFileDependencyGroup(LockFile.ToolFramework.ToString(), new []
{
"DependencyA >= 1.0.0",
"DependencyB >= 2.0.0",
})
}
};
var packageSpec = new PackageSpec(new JObject())
{
Dependencies = new List<LibraryDependency>(),
Tools = new List<ToolDependency>
{
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyB",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("2.0.0")
}
},
new ToolDependency
{
LibraryRange = new LibraryRange
{
Name = "DependencyA",
TypeConstraint = LibraryDependencyTarget.Package,
VersionRange = VersionRange.Parse("1.0.0")
}
}
}
};
// Act & Assert
Assert.True(lockFile.IsValidForPackageSpec(packageSpec));
}
}
}