diff --git a/dir.props b/dir.props
index 30e689a2af..d43067d219 100644
--- a/dir.props
+++ b/dir.props
@@ -69,6 +69,7 @@
$(PackageReportDir)prebuilt-usage.json
$(PackageReportDir)prodcon-build.xml
$(PackageReportDir)poisoned.txt
+ $(BaseOutputPath)conflict-report/
diff --git a/repos/dir.targets b/repos/dir.targets
index b0c30555db..a0bdbb0313 100644
--- a/repos/dir.targets
+++ b/repos/dir.targets
@@ -7,6 +7,7 @@
+
@@ -155,7 +156,8 @@
- <_PreviouslySourceBuiltPackages Include="$(SourceBuiltPackagesPath)*.nupkg" />
+ <_PreviouslySourceBuiltPackages Include="$(SourceBuiltPackagesPath)*.nupkg"
+ Exclude="$(SourceBuiltPackagesPath)*.symbols.nupkg" />
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ReportDir>$(ConflictingPackageReportDir)before-$(RepositoryName)/
+ <_ReportDataFile>$(_ReportDir)usage.json
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
@@ -354,6 +374,19 @@
DataFile="$(PackageReportDataFile)" />
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools-local/tasks/Microsoft.DotNet.SourceBuild.Tasks/GetSourceBuiltNupkgCacheConflicts.cs b/tools-local/tasks/Microsoft.DotNet.SourceBuild.Tasks/GetSourceBuiltNupkgCacheConflicts.cs
new file mode 100644
index 0000000000..77f8eedfb4
--- /dev/null
+++ b/tools-local/tasks/Microsoft.DotNet.SourceBuild.Tasks/GetSourceBuiltNupkgCacheConflicts.cs
@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Microsoft.DotNet.SourceBuild.Tasks
+{
+ ///
+ /// For each source-built nupkg info given, ensure that if the package cache contains a package
+ /// with the same id and version, the cached nupkg is the same as the source-built one.
+ ///
+ /// If the package cache contains a package with the same package id and version as a
+ /// source-built one, nuget restore short-circuits and doesn't look for the source-built one.
+ /// This usually results in prebuilt packages being used, which can either break the build or
+ /// end up in the outputs.
+ ///
+ public class GetSourceBuiltNupkgCacheConflicts : Task
+ {
+ ///
+ /// Items containing package id and version of each source-built package.
+ /// ReadNuGetPackageInfos is recommended to generate these.
+ ///
+ /// %(Identity): Path to the original nupkg.
+ /// %(PackageId): Identity of the package.
+ /// %(PackageVersion): Version of the package.
+ ///
+ [Required]
+ public ITaskItem[] SourceBuiltPackageInfos { get; set; }
+
+ ///
+ /// Package cache dir containing nupkgs to compare. Path is expected to be like:
+ ///
+ /// {PackageCacheDir}/{lowercase id}/{version}/{lowercase id}.{version}.nupkg
+ ///
+ [Required]
+ public string PackageCacheDir { get; set; }
+
+ [Output]
+ public ITaskItem[] ConflictingPackageInfos { get; set; }
+
+ public override bool Execute()
+ {
+ DateTime startTime = DateTime.Now;
+
+ ConflictingPackageInfos = SourceBuiltPackageInfos
+ .Where(item =>
+ {
+ string sourceBuiltPath = item.ItemSpec;
+ string id = item.GetMetadata("PackageId");
+ string version = item.GetMetadata("PackageVersion");
+
+ string packageCachePath = Path.Combine(
+ PackageCacheDir,
+ id.ToLowerInvariant(),
+ version,
+ $"{id.ToLowerInvariant()}.{version}.nupkg");
+
+ if (!File.Exists(packageCachePath))
+ {
+ Log.LogMessage(
+ MessageImportance.Low,
+ $"OK: Package not found in package cache: {id} {version}");
+ return false;
+ }
+
+ Log.LogMessage(
+ MessageImportance.Low,
+ $"Package id/version found in package cache, verifying: {id} {version}");
+
+ bool identical = File.ReadAllBytes(sourceBuiltPath)
+ .SequenceEqual(File.ReadAllBytes(packageCachePath));
+
+ if (!identical)
+ {
+ Log.LogMessage(
+ MessageImportance.Low,
+ "BAD: Source-built nupkg is not byte-for-byte identical " +
+ $"to nupkg in cache: {id} {version}");
+ return true;
+ }
+
+ Log.LogMessage(
+ MessageImportance.Low,
+ $"OK: Package in cache is identical to source-built: {id} {version}");
+ return false;
+ })
+ .ToArray();
+
+ // Tell the user about this task, in case it takes a while.
+ Log.LogMessage(
+ MessageImportance.High,
+ "Checked cache for conflicts with source-built nupkgs. " +
+ $"Took {DateTime.Now - startTime}");
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}