Skip to content

Commit 9d81590

Browse files
authored
Fix duplicate resolved files to publish (#3320)
1 parent 8e7f7cf commit 9d81590

File tree

3 files changed

+50
-9
lines changed

3 files changed

+50
-9
lines changed

src/Tasks/Common/ConflictResolution/ConflictResolver.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,25 @@ private TConflictItem ResolveConflict(TConflictItem item1, TConflictItem item2,
349349
return item2;
350350
}
351351

352+
if (item1.ItemType == ConflictItemType.CopyLocal && item2.ItemType == ConflictItemType.CopyLocal)
353+
{
354+
// If two items are copy local, we must pick one even if versions are identical, as only
355+
// one of them can be copied locally. The policy here must be deterministic, but it can
356+
// be chosen arbitrarily. The assumption is that the assemblies are fully semantically
357+
// equivalent.
358+
//
359+
// We choose ordinal string comparison of package id as a final tie-breaker for this case.
360+
// We will get here in the real case of frameworks with overlapping assemblies (including
361+
// version) and self-contained apps. The assembly we choose here is not guaranteed to match
362+
// the assembly that would be chosen by the host for a framework-dependent app. The host
363+
// is free to make its own deterministic but arbitrary choice.
364+
int cmp = string.CompareOrdinal(item1.PackageId, item2.PackageId);
365+
if (cmp != 0)
366+
{
367+
return cmp < 0 ? item1 : item2;
368+
}
369+
}
370+
352371
if (logUnresolvedConflicts)
353372
{
354373
string message = conflictMessage + SENTENCE_SPACING + string.Format(CultureInfo.CurrentCulture, Strings.ConflictCouldNotDetermineWinner);

src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,8 +553,8 @@ Copyright (c) .NET Foundation. All rights reserved.
553553
<ItemGroup>
554554
<_ResolvedCopyLocalPublishAssets Include="@(ReferenceCopyLocalPaths)"
555555
Exclude="@(_ResolvedCopyLocalBuildAssets);@(RuntimePackAsset)"
556-
Condition="'$(PublishReferencesDocumentationFiles)' == 'true' or '%(Extension)' != '.xml'">
557-
<DestinationSubPath>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(Filename)%(Extension)</DestinationSubPath>
556+
Condition="'$(PublishReferencesDocumentationFiles)' == 'true' or '%(ReferenceCopyLocalPaths.Extension)' != '.xml'">
557+
<DestinationSubPath>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</DestinationSubPath>
558558
</_ResolvedCopyLocalPublishAssets>
559559
</ItemGroup>
560560

src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,14 @@ public void It_fails_if_nobuild_was_requested_but_build_was_invoked()
501501
.HaveStdOutContaining("NETSDK1085");
502502
}
503503

504-
[Fact]
505-
public void It_contains_no_duplicates_in_resolved_publish_assets()
504+
[WindowsOnlyFact]
505+
public void It_contains_no_duplicates_in_resolved_publish_assets_on_windows()
506+
=> It_contains_no_duplicates_in_resolved_publish_assets("windows");
507+
508+
[Theory]
509+
[InlineData("console")]
510+
[InlineData("web")]
511+
public void It_contains_no_duplicates_in_resolved_publish_assets(string type)
506512
{
507513
// Use a specific RID to guarantee a consistent set of assets
508514
var testProject = new TestProject()
@@ -514,9 +520,25 @@ public void It_contains_no_duplicates_in_resolved_publish_assets()
514520
IsExe = true
515521
};
516522

523+
switch (type)
524+
{
525+
case "windows":
526+
testProject.ProjectSdk = "Microsoft.NET.Sdk.WindowsDesktop";
527+
testProject.AdditionalProperties.Add("UseWpf", "true");
528+
testProject.AdditionalProperties.Add("UseWindowsForms", "true");
529+
break;
530+
case "console":
531+
break;
532+
case "web":
533+
testProject.ProjectSdk = "Microsoft.NET.Sdk.Web";
534+
break;
535+
default:
536+
throw new ArgumentOutOfRangeException(nameof(type));
537+
}
538+
517539
testProject.PackageReferences.Add(new TestPackageReference("NewtonSoft.Json", "9.0.1"));
518540

519-
var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name)
541+
var testAsset = _testAssetsManager.CreateTestProject(testProject, testProject.Name, identifier: type)
520542
.WithProjectChanges(project =>
521543
{
522544
project.Root.Add(XElement.Parse(@"
@@ -526,12 +548,12 @@ public void It_contains_no_duplicates_in_resolved_publish_assets()
526548
</RemoveDuplicates>
527549
<Message Condition=""'@(_ResolvedCopyLocalPublishAssets)' != '@(FilteredAssets)'"" Importance=""High"" Text=""Duplicate items are present in: @(_ResolvedCopyLocalPublishAssets)!"" />
528550
<ItemGroup>
529-
<AssetFilenames Include=""@(_ResolvedCopyLocalPublishAssets->'%(Filename)%(Extension)')"" />
551+
<AssetDestinationSubPaths Include=""@(_ResolvedCopyLocalPublishAssets->'%(DestinationSubPath)')"" />
530552
</ItemGroup>
531-
<RemoveDuplicates Inputs=""@(AssetFilenames)"">
532-
<Output TaskParameter=""Filtered"" ItemName=""FilteredAssetFilenames""/>
553+
<RemoveDuplicates Inputs=""@(AssetDestinationSubPaths)"">
554+
<Output TaskParameter=""Filtered"" ItemName=""FilteredAssetDestinationSubPaths""/>
533555
</RemoveDuplicates>
534-
<Message Condition=""'@(AssetFilenames)' != '@(FilteredAssetFilenames)'"" Importance=""High"" Text=""Duplicate filenames are present in: @(_ResolvedCopyLocalPublishAssets)!"" />
556+
<Message Condition=""'@(AssetDestinationSubPaths)' != '@(FilteredAssetDestinationSubPaths)'"" Importance=""High"" Text=""Duplicate DestinationSubPaths are present in: @(AssetDestinationSubPaths)!"" />
535557
</Target>"));
536558
})
537559
.Restore(Log, testProject.Name);

0 commit comments

Comments
 (0)