Skip to content

Commit

Permalink
Fix packages.config to project mapping in NuGet.exe (#5818) (#5831)
Browse files Browse the repository at this point in the history
  • Loading branch information
nkolev92 authored May 31, 2024
1 parent 31fbd17 commit a6b8c73
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 10 deletions.
26 changes: 17 additions & 9 deletions src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,24 +276,24 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac

if (packageRestoreInputs.RestoringWithSolutionFile)
{
Dictionary<string, string> configToProjectPath = GetPackagesConfigToProjectPath(packageRestoreInputs);
Dictionary<string, HashSet<string>> configToProjectPath = GetPackagesConfigToProjectsPath(packageRestoreInputs);
Dictionary<PackageReference, List<string>> packageReferenceToProjects = new(PackageReferenceComparer.Instance);

foreach (string configFile in packageRestoreInputs.PackagesConfigFiles)
foreach (string configFile in packageRestoreInputs.PackagesConfigFiles.Distinct())
{
foreach (PackageReference packageReference in GetInstalledPackageReferences(configFile))
{
if (!configToProjectPath.TryGetValue(configFile, out string projectPath))
if (!configToProjectPath.TryGetValue(configFile, out HashSet<string> projectPath))
{
projectPath = configFile;
projectPath = new HashSet<string> { configFile };
}

if (!packageReferenceToProjects.TryGetValue(packageReference, out List<string> value))
{
value ??= new();
packageReferenceToProjects.Add(packageReference, value);
}
value.Add(projectPath);
value.AddRange(projectPath);
}
}

Expand Down Expand Up @@ -469,17 +469,25 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac
}
}

private static Dictionary<string, string> GetPackagesConfigToProjectPath(PackageRestoreInputs packageRestoreInputs)
private Dictionary<string, HashSet<string>> GetPackagesConfigToProjectsPath(PackageRestoreInputs packageRestoreInputs)
{
Dictionary<string, string> configToProjectPath = new();
Dictionary<string, HashSet<string>> configToProjectPath = new();
foreach (PackageSpec project in packageRestoreInputs.ProjectReferenceLookup.Projects)
{
if (project.RestoreMetadata?.ProjectStyle == ProjectStyle.PackagesConfig)
{
configToProjectPath.Add(((PackagesConfigProjectRestoreMetadata)project.RestoreMetadata).PackagesConfigPath, project.FilePath);
var packagesConfig = ((PackagesConfigProjectRestoreMetadata)project.RestoreMetadata).PackagesConfigPath;

if (configToProjectPath.TryGetValue(packagesConfig, out HashSet<string> existingValue))
{
existingValue.Add(project.FilePath);
}
else
{
configToProjectPath.Add(packagesConfig, new HashSet<string> { project.FilePath });
}
}
}

return configToProjectPath;
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/NuGet.Core/NuGet.Build.Tasks/BuildTasksUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ private static async Task<RestoreSummary> PerformNuGetV2RestoreAsync(Common.ILog
value ??= new();
packageReferenceToProjects.Add(packageReference, value);
}
value.Add(pcRestoreMetadata.PackagesConfigPath);
value.Add(packageSpec.FilePath);
}
restoreAuditProperties.Add(packageSpec.FilePath, packageSpec.RestoreMetadata.RestoreAuditProperties);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3573,6 +3573,79 @@ public void RestoreCommand_WithSolutionFile_PackageWithVulnerabilities_RaisesApp
r.AllOutput.Should().NotContain($"Package 'packageA' 1.1.0 has a known high severity vulnerability");
}

[SkipMono()]
public void RestoreCommand_WithMultipleProjectsInSameDirectory_RaisesAppropriateWarnings()
{
// Arrange
var nugetexe = Util.GetNuGetExePath();
using var pathContext = new SimpleTestPathContext();
using var mockServer = new FileSystemBackedV3MockServer(pathContext.PackageSource, sourceReportsVulnerabilities: true);

mockServer.Vulnerabilities.Add(
"packageA",
new List<(Uri, PackageVulnerabilitySeverity, VersionRange)> {
(new Uri("https://contoso.com/advisories/12345"), PackageVulnerabilitySeverity.High, VersionRange.Parse("[1.0.0, 2.0.0)")),
(new Uri("https://contoso.com/advisories/12346"), PackageVulnerabilitySeverity.Critical, VersionRange.Parse("[1.2.0, 2.0.0)"))
});
pathContext.Settings.RemoveSource("source");
pathContext.Settings.AddSource("source", mockServer.ServiceIndexUri);

Util.CreateTestPackage("packageA", "1.1.0", pathContext.PackageSource);
Util.CreateTestPackage("packageA", "1.2.0", pathContext.PackageSource);
Util.CreateTestPackage("packageB", "2.2.0", pathContext.PackageSource);

var solution = new SimpleTestSolutionContext(pathContext.SolutionRoot);
var projectA = new SimpleTestProjectContext(
"a",
ProjectStyle.PackagesConfig,
pathContext.SolutionRoot);
projectA.Properties.Add("NuGetAuditLevel", "critical");

var projectB = new SimpleTestProjectContext(
"b",
ProjectStyle.PackagesConfig,
pathContext.SolutionRoot);
projectB.Properties.Add("NuGetAuditLevel", "high");
projectB.ProjectPath = Path.Combine(pathContext.SolutionRoot, "a", $"b.csproj");

solution.Projects.Add(projectA);
solution.Projects.Add(projectB);
solution.Create(pathContext.SolutionRoot);

Util.CreateFile(Path.GetDirectoryName(projectA.ProjectPath), "packages.config",
@"<packages>
<package id=""packageA"" version=""1.1.0"" />
<package id=""packageA"" version=""1.2.0"" />
<package id=""packageB"" version=""2.2.0"" />
</packages>");
mockServer.Start();

// Act
var r = CommandRunner.Run(
nugetexe,
pathContext.WorkingDirectory,
$"restore {solution.SolutionPath}");

mockServer.Stop();

// Assert
r.Success.Should().BeTrue(because: r.AllOutput);
var packageFileA = Path.Combine(pathContext.SolutionRoot, "packages", "packageA.1.1.0", "packageA.1.1.0.nupkg");
var packageFileA120 = Path.Combine(pathContext.SolutionRoot, "packages", "packageA.1.2.0", "packageA.1.2.0.nupkg");
var packageFileB = Path.Combine(pathContext.SolutionRoot, "packages", "packageB.2.2.0", "packageB.2.2.0.nupkg");
File.Exists(packageFileA).Should().BeTrue();
File.Exists(packageFileA120).Should().BeTrue();
File.Exists(packageFileB).Should().BeTrue();
r.AllOutput.Should().Contain($"Package 'packageA' 1.2.0 has a known critical severity vulnerability", Exactly.Twice());
r.AllOutput.Should().Contain($"Package 'packageA' 1.2.0 has a known high severity vulnerability", Exactly.Once());
r.AllOutput.Should().Contain($"Package 'packageA' 1.1.0 has a known high severity vulnerability", Exactly.Once());
// Make sure that we're not missing out asserting any reported vulnerabilities.
r.AllOutput.Should().NotContain($"a known low severity vulnerability");
r.AllOutput.Should().NotContain($"a known moderate severity vulnerability");
r.AllOutput.Should().Contain($"a known high severity vulnerability", Exactly.Twice());
r.AllOutput.Should().Contain($"a known critical severity vulnerability", Exactly.Twice());
}

private static byte[] GetResource(string name)
{
return ResourceTestUtility.GetResourceBytes(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Protocol;
using NuGet.Test.Utility;
using NuGet.Versioning;
using Test.Utility;
using Xunit;

namespace Msbuild.Integration.Test
Expand Down Expand Up @@ -1428,5 +1430,101 @@ static DateTime GetFileLastWriteTime(string path)
return fileInfo.LastWriteTimeUtc;
}
}

[Fact]
public async Task MsbuildRestore_WithMultipleProjectsInSameDirectory_RaisesAppropriateWarnings()
{
// Arrange
using var pathContext = new SimpleTestPathContext();
using var mockServer = new FileSystemBackedV3MockServer(pathContext.PackageSource, sourceReportsVulnerabilities: true);

mockServer.Vulnerabilities.Add(
"packageA",
new List<(Uri, PackageVulnerabilitySeverity, VersionRange)> {
(new Uri("https://contoso.com/advisories/12345"), PackageVulnerabilitySeverity.High, VersionRange.Parse("[1.0.0, 2.0.0)")),
(new Uri("https://contoso.com/advisories/12346"), PackageVulnerabilitySeverity.Critical, VersionRange.Parse("[1.2.0, 2.0.0)"))
});
pathContext.Settings.RemoveSource("source");
pathContext.Settings.AddSource("source", mockServer.ServiceIndexUri);

var packageA1 = new SimpleTestPackageContext()
{
Id = "packageA",
Version = "1.1.0"
};
var packageA2 = new SimpleTestPackageContext()
{
Id = "packageA",
Version = "1.2.0"
};
var packageB = new SimpleTestPackageContext()
{
Id = "packageB",
Version = "2.2.0"
};

await SimpleTestPackageUtility.CreatePackagesAsync(
pathContext.PackageSource,
packageA1,
packageA2,
packageB);

var solution = new SimpleTestSolutionContext(pathContext.SolutionRoot);
var projectA = new SimpleTestProjectContext(
"a",
ProjectStyle.PackagesConfig,
pathContext.SolutionRoot);
projectA.Properties.Add("NuGetAuditLevel", "critical");

var projectB = new SimpleTestProjectContext(
"b",
ProjectStyle.PackagesConfig,
pathContext.SolutionRoot);
projectB.Properties.Add("NuGetAuditLevel", "high");
projectB.ProjectPath = Path.Combine(pathContext.SolutionRoot, "a", $"b.csproj");

solution.Projects.Add(projectA);
solution.Projects.Add(projectB);
solution.Create(pathContext.SolutionRoot);


using (var writer = new StreamWriter(Path.Combine(Path.GetDirectoryName(projectB.ProjectPath), "packages.config")))
{
writer.Write(
@"<packages>
<package id=""packageA"" version=""1.1.0"" />
<package id=""packageA"" version=""1.2.0"" />
<package id=""packageB"" version=""2.2.0"" />
</packages>");
}
mockServer.Start();

// Act
string args = $"/t:restore {pathContext.SolutionRoot} /p:RestorePackagesConfig=true";
CommandRunnerResult r = _msbuildFixture.RunMsBuild(pathContext.WorkingDirectory, args, ignoreExitCode: true);

mockServer.Stop();

// Assert
r.Success.Should().BeTrue(because: r.AllOutput);
var packageFileA = Path.Combine(pathContext.SolutionRoot, "packages", "packageA.1.1.0", "packageA.1.1.0.nupkg");
var packageFileA120 = Path.Combine(pathContext.SolutionRoot, "packages", "packageA.1.2.0", "packageA.1.2.0.nupkg");
var packageFileB = Path.Combine(pathContext.SolutionRoot, "packages", "packageB.2.2.0", "packageB.2.2.0.nupkg");
File.Exists(packageFileA).Should().BeTrue();
File.Exists(packageFileA120).Should().BeTrue();
File.Exists(packageFileB).Should().BeTrue();

// MSBuild replays warnings at the bottom, so only look there.
var replayedOutput = r.AllOutput.Substring(r.AllOutput.IndexOf($"\"{solution.SolutionPath}\" (Restore "));

replayedOutput.Should().Contain($"Package 'packageA' 1.2.0 has a known critical severity vulnerability", Exactly.Twice());
replayedOutput.Should().Contain($"Package 'packageA' 1.2.0 has a known high severity vulnerability", Exactly.Once());
replayedOutput.Should().Contain($"Package 'packageA' 1.1.0 has a known high severity vulnerability", Exactly.Once());
// Make sure that we're not missing out asserting any reported vulnerabilities.
replayedOutput.Should().NotContain($"a known low severity vulnerability");
replayedOutput.Should().NotContain($"a known moderate severity vulnerability");
replayedOutput.Should().Contain($"a known high severity vulnerability", Exactly.Twice());
replayedOutput.Should().Contain($"a known critical severity vulnerability", Exactly.Twice());
}
}
}

0 comments on commit a6b8c73

Please sign in to comment.