Skip to content

Commit

Permalink
Handle duplicate MSBuild items during restore
Browse files Browse the repository at this point in the history
This fix improves the robustness of Restore, allowing it to gracefully handle when MSBuild generates duplicate items for the same project.

Fixes NuGet/Home#4316
  • Loading branch information
emgarten committed Feb 9, 2017
1 parent a192c0d commit 9d22672
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ public static DependencyGraphSpec GetDependencySpec(IEnumerable<IMSBuildItem> it
}

// Add projects
foreach (var spec in itemsById.Values.Select(GetPackageSpec))
var validProjectSpecs = itemsById.Values.Select(GetPackageSpec).Where(e => e != null);

foreach (var spec in validProjectSpecs)
{
if (spec.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference
|| spec.RestoreMetadata.ProjectStyle == ProjectStyle.ProjectJson
Expand Down Expand Up @@ -118,7 +120,10 @@ public static PackageSpec GetPackageSpec(IEnumerable<IMSBuildItem> items)

PackageSpec result = null;

var specItem = items.SingleOrDefault(item =>
// There should only be one ProjectSpec per project in the item set,
// but if multiple do appear take only the first one in an effort
// to handle this gracefully.
var specItem = items.FirstOrDefault(item =>
"projectSpec".Equals(item.GetProperty("Type"),
StringComparison.OrdinalIgnoreCase));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,169 @@ public void MSBuildRestoreUtility_GetPackageSpec_NetCore_Conditionals()
}
}

[Fact]
public void MSBuildRestoreUtility_GetPackageSpec_NetCore_VerifyDuplicateItemsAreIgnored()
{
using (var workingDir = TestDirectory.Create())
{
// Arrange
var projectRoot = Path.Combine(workingDir, "a");
var projectPath = Path.Combine(projectRoot, "a.csproj");
var outputPath = Path.Combine(projectRoot, "obj");

var items = new List<IDictionary<string, string>>();

var specItem = new Dictionary<string, string>()
{
{ "Type", "ProjectSpec" },
{ "ProjectName", "a" },
{ "ProjectStyle", "PackageReference" },
{ "OutputPath", outputPath },
{ "ProjectUniqueName", "482C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "ProjectPath", projectPath },
{ "TargetFrameworks", "net46;netstandard1.6" },
{ "CrossTargeting", "true" },
};

// Add each item twice
items.Add(specItem);
items.Add(specItem);

// A -> B
var projectRef = new Dictionary<string, string>()
{
{ "Type", "ProjectReference" },
{ "ProjectUniqueName", "482C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "ProjectReferenceUniqueName", "AA2C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "ProjectPath", "otherProjectPath.csproj" },
{ "TargetFrameworks", "netstandard1.6" },
{ "CrossTargeting", "true" },
};

items.Add(projectRef);
items.Add(projectRef);

// Package references
// A netstandard1.6 -> Z
var packageRef1 = new Dictionary<string, string>()
{
{ "Type", "Dependency" },
{ "ProjectUniqueName", "482C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "Id", "z" },
{ "VersionRange", "2.0.0" },
{ "TargetFrameworks", "netstandard1.6" },
{ "CrossTargeting", "true" },
};

items.Add(packageRef1);
items.Add(packageRef1);

// B ALL -> Y
var packageRef2 = new Dictionary<string, string>()
{
{ "Type", "Dependency" },
{ "ProjectUniqueName", "482C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "Id", "y" },
{ "VersionRange", "[1.0.0]" },
{ "TargetFrameworks", "netstandard1.6;net46" },
{ "CrossTargeting", "true" },
};

items.Add(packageRef2);
items.Add(packageRef2);

// Framework assembly
var frameworkAssembly = new Dictionary<string, string>()
{
{ "Type", "FrameworkAssembly" },
{ "ProjectUniqueName", "482C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "Id", "System.IO" },
{ "TargetFrameworks", "net46" },
{ "CrossTargeting", "true" },
};

items.Add(frameworkAssembly);
items.Add(frameworkAssembly);

// TFM info
var tfmInfo = new Dictionary<string, string>()
{
{ "Type", "TargetFrameworkInformation" },
{ "ProjectUniqueName", "482C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "PackageTargetFallback", "portable-net45+win8;dnxcore50;;" },
{ "TargetFramework", "netstandard16" }
};

items.Add(tfmInfo);
items.Add(tfmInfo);

var wrappedItems = items.Select(CreateItems).ToList();

// Act
var dgSpec = MSBuildRestoreUtility.GetDependencySpec(wrappedItems);
var projectSpec = dgSpec.Projects.Single(e => e.Name == "a");

// Assert
Assert.Equal(0, projectSpec.Dependencies.Count);
Assert.Equal(1, dgSpec.Projects.Count);
Assert.Equal("System.IO|y", string.Join("|", projectSpec.GetTargetFramework(NuGetFramework.Parse("net46")).Dependencies.Select(e => e.Name)));
Assert.Equal("z|y", string.Join("|", projectSpec.GetTargetFramework(NuGetFramework.Parse("netstandard1.6")).Dependencies.Select(e => e.Name)));
Assert.Equal(2, projectSpec.RestoreMetadata.TargetFrameworks.Count);
}
}

[Fact]
public void MSBuildRestoreUtility_GetPackageSpec_NetCore_IgnoreBadItemWithMismatchedIds()
{
using (var workingDir = TestDirectory.Create())
{
// Arrange
var projectRoot = Path.Combine(workingDir, "a");
var projectPath = Path.Combine(projectRoot, "a.csproj");
var outputPath = Path.Combine(projectRoot, "obj");

var items = new List<IDictionary<string, string>>();

var specItem = new Dictionary<string, string>()
{
{ "Type", "ProjectSpec" },
{ "ProjectName", "a" },
{ "ProjectStyle", "PackageReference" },
{ "OutputPath", outputPath },
{ "ProjectUniqueName", "AA2C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "ProjectPath", projectPath },
{ "TargetFrameworks", "net46;netstandard1.6" },
{ "CrossTargeting", "true" },
};

items.Add(specItem);

// A -> B
var projectRef = new Dictionary<string, string>()
{
{ "Type", "ProjectReference" },

// This ID does not match the project!
{ "ProjectUniqueName", "BB2C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "ProjectReferenceUniqueName", "CC2C20DE-DFF9-4BD0-B90A-BD3201AA351A" },
{ "ProjectPath", "otherProjectPath.csproj" },
{ "TargetFrameworks", "netstandard1.6" },
{ "CrossTargeting", "true" },
};

items.Add(projectRef);

var wrappedItems = items.Select(CreateItems).ToList();

// Act
var dgSpec = MSBuildRestoreUtility.GetDependencySpec(wrappedItems);
var projectSpec = dgSpec.Projects.Single(e => e.Name == "a");

// Assert
Assert.NotNull(projectSpec);
}
}

[Fact]
public void MSBuildRestoreUtility_GetPackageSpec_UAP_P2P()
{
Expand Down

0 comments on commit 9d22672

Please sign in to comment.