Skip to content

Commit

Permalink
Check for duplicate NuGet items, and raise warnings/errors where appr…
Browse files Browse the repository at this point in the history
…opriate (#4484)
  • Loading branch information
nkolev92 authored Mar 31, 2022
1 parent 972895d commit a1afdef
Show file tree
Hide file tree
Showing 13 changed files with 994 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/NuGet.Clients/NuGet.CommandLine/MsBuildUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ public static string GetMSBuildArguments(
// Override the target under ImportsAfter with the current NuGet.targets version.
AddProperty(args, "NuGetRestoreTargets", entryPointTargetPath);
AddProperty(args, "RestoreUseCustomAfterTargets", bool.TrueString);
AddProperty(args, "DisableCheckingDuplicateNuGetItems", bool.TrueString);

// Set path to nuget.exe or the build task
AddProperty(args, "RestoreTaskAssemblyFile", nugetExePath);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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 System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Build.Framework;
using Newtonsoft.Json;
using NuGet.Commands;
using NuGet.Common;
using NuGet.ProjectModel;
using Task = Microsoft.Build.Utilities.Task;


namespace NuGet.Build.Tasks
{
public class CheckForDuplicateNuGetItemsTask : Task
{
[Required]
public ITaskItem[] Items { get; set; }

[Required]
public string ItemName { get; set; }

[Required]
public string LogCode { get; set; }

[Required]
public string MSBuildProjectFullPath { get; set; }

public string TreatWarningsAsErrors { get; set; }

public string WarningsAsErrors { get; set; }

public string NoWarn { get; set; }

[Output]
public ITaskItem[] DeduplicatedItems { get; set; }

public override bool Execute()
{
DeduplicatedItems = Array.Empty<ITaskItem>();
var itemGroups = Items.GroupBy(i => i.ItemSpec, StringComparer.OrdinalIgnoreCase);
var duplicateItems = itemGroups.Where(g => g.Count() > 1).ToList();

if (duplicateItems.Any())
{
var logger = new PackCollectorLogger(
new MSBuildLogger(Log),
EvaluateWarningProperties(WarningsAsErrors, NoWarn, TreatWarningsAsErrors)
);
string duplicateItemsFormatted = string.Join("; ", duplicateItems.Select(d => string.Join(", ", d.Select(e => $"{e.ItemSpec} {e.GetMetadata("version")}"))));
NuGetLogCode logCode = (NuGetLogCode)Enum.Parse(typeof(NuGetLogCode), LogCode);

logger.Log(new RestoreLogMessage(
LogLevel.Warning,
logCode,
string.Format(
CultureInfo.CurrentCulture,
Strings.Error_DuplicateItems,
ItemName,
duplicateItemsFormatted))
{
FilePath = MSBuildProjectFullPath,
});

// Set Output
DeduplicatedItems = itemGroups.Select(g => g.First()).ToArray();
}

return !Log.HasLoggedErrors;
}

private WarningProperties EvaluateWarningProperties(string warningsAsErrors, string noWarn, string treatWarningsAsErrors)
{
var warnAsErrorCodes = new HashSet<NuGetLogCode>();
ReadNuGetLogCodes(warningsAsErrors, warnAsErrorCodes);
var noWarnCodes = new HashSet<NuGetLogCode>();
ReadNuGetLogCodes(noWarn, noWarnCodes);
_ = bool.TryParse(treatWarningsAsErrors, out bool allWarningsAsErrors);

return new WarningProperties(warnAsErrorCodes, noWarnCodes, allWarningsAsErrors);
}

private static void ReadNuGetLogCodes(string str, HashSet<NuGetLogCode> hashCodes)
{
foreach (var code in MSBuildStringUtility.Split(str))
{
if (Enum.TryParse(code, out NuGetLogCode logCode))
{
hashCodes.Add(logCode);
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/NuGet.Core/NuGet.Build.Tasks/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@
[assembly: SuppressMessage("Build", "CA1819:Properties should not return arrays", Justification = "<Pending>", Scope = "member", Target = "~P:NuGet.Build.Tasks.WarnForInvalidProjectsTask.ValidProjects")]
[assembly: SuppressMessage("Build", "CA1819:Properties should not return arrays", Justification = "<Pending>", Scope = "member", Target = "~P:NuGet.Build.Tasks.WriteRestoreGraphTask.RestoreGraphItems")]
[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.Build.Tasks.GetReferenceNearestTargetFrameworkTask.TryParseFramework(System.String,System.String,System.String,NuGet.Build.MSBuildLogger,NuGet.Frameworks.NuGetFramework@)~System.Boolean")]
[assembly: SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "MSBuild tasks expect arrays.", Scope = "member", Target = "~P:NuGet.Build.Tasks.CheckForDuplicateNuGetItemsTask.Items")]
[assembly: SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "MSBuild tasks expect arrays.", Scope = "member", Target = "~P:NuGet.Build.Tasks.CheckForDuplicateNuGetItemsTask.DeduplicatedItems")]
87 changes: 83 additions & 4 deletions src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<UsingTask TaskName="NuGet.Build.Tasks.GetReferenceNearestTargetFrameworkTask" AssemblyFile="$(RestoreTaskAssemblyFile)" />
<UsingTask TaskName="NuGet.Build.Tasks.GetRestoreProjectStyleTask" AssemblyFile="$(RestoreTaskAssemblyFile)" />
<UsingTask TaskName="NuGet.Build.Tasks.NuGetMessageTask" AssemblyFile="$(RestoreTaskAssemblyFile)" />
<UsingTask TaskName="NuGet.Build.Tasks.CheckForDuplicateNuGetItemsTask" AssemblyFile="$(RestoreTaskAssemblyFile)" />

<!--
============================================================
Expand Down Expand Up @@ -173,15 +174,68 @@ Copyright (c) .NET Foundation. All rights reserved.
package references before NuGet reads them.
============================================================
-->
<Target Name="CollectPackageReferences" Returns="@(PackageReference)" />
<Target Name="CollectPackageReferences" Returns="@(PackageReference)" >
<!-- NOTE for design-time builds we need to ensure that we continue on error. -->
<PropertyGroup>
<CollectPackageReferencesContinueOnError>$(ContinueOnError)</CollectPackageReferencesContinueOnError>
<CollectPackageReferencesContinueOnError Condition="'$(ContinueOnError)' == '' ">false</CollectPackageReferencesContinueOnError>
</PropertyGroup>

<CheckForDuplicateNuGetItemsTask
Condition="'$(DisableCheckingDuplicateNuGetItems)' != 'true' "
Items="@(PackageReference)"
ItemName="PackageReference"
LogCode="NU1504"
MSBuildProjectFullPath="$(MSBuildProjectFullPath)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
WarningsAsErrors="$(WarningsAsErrors)"
NoWarn="$(NoWarn)"
ContinueOnError="$(CollectPackageReferencesContinueOnError)"
>
<Output TaskParameter="DeduplicatedItems" ItemName="DeduplicatedPackageReferences" />
</CheckForDuplicateNuGetItemsTask>

<ItemGroup Condition="'@(DeduplicatedPackageReferences)' != ''">
<PackageReference Remove="@(PackageReference)" />
<PackageReference Include="@(DeduplicatedPackageReferences)" />
</ItemGroup>

</Target>

<!--
============================================================
CollectCentralPackageVersions
Gathers all PackageVersion items from the central package versions file.
============================================================
-->
<Target Name="CollectCentralPackageVersions" Returns="@(PackageVersion)" />
<Target Name="CollectCentralPackageVersions" Returns="@(PackageVersion)">

<!-- NOTE for design-time builds we need to ensure that we continue on error. -->
<PropertyGroup>
<CollectCentralPackageVersionsContinueOnError>$(ContinueOnError)</CollectCentralPackageVersionsContinueOnError>
<CollectCentralPackageVersionsContinueOnError Condition="'$(ContinueOnError)' == '' ">false</CollectCentralPackageVersionsContinueOnError>
</PropertyGroup>

<CheckForDuplicateNuGetItemsTask
Condition="'$(DisableCheckingDuplicateNuGetItems)' != 'true' "
Items="@(PackageVersion)"
ItemName="PackageVersion"
LogCode="NU1506"
MSBuildProjectFullPath="$(MSBuildProjectFullPath)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
WarningsAsErrors="$(WarningsAsErrors)"
NoWarn="$(NoWarn)"
ContinueOnError="$(CollectCentralPackageVersionsContinueOnError)"
>
<Output TaskParameter="DeduplicatedItems" ItemName="DeduplicatedPackageVersions" />
</CheckForDuplicateNuGetItemsTask>

<ItemGroup Condition="'@(DeduplicatedPackageVersions)' != ''">
<PackageVersion Remove="@(PackageVersion)" />
<PackageVersion Include="@(DeduplicatedPackageVersions)" />
</ItemGroup>

</Target>

<!--
============================================================
Expand All @@ -191,7 +245,32 @@ Copyright (c) .NET Foundation. All rights reserved.
package downloads before NuGet reads them.
============================================================
-->
<Target Name="CollectPackageDownloads" Returns="@(PackageDownload)" />
<Target Name="CollectPackageDownloads" Returns="@(PackageDownload)">
<!-- NOTE for design-time builds we need to ensure that we continue on error. -->
<PropertyGroup>
<CollectPackageDownloadsContinueOnError>$(ContinueOnError)</CollectPackageDownloadsContinueOnError>
<CollectPackageDownloadsContinueOnError Condition="'$(ContinueOnError)' == '' ">false</CollectPackageDownloadsContinueOnError>
</PropertyGroup>

<CheckForDuplicateNuGetItemsTask
Condition="'$(DisableCheckingDuplicateNuGetItems)' != 'true' "
Items="@(PackageDownload)"
ItemName="PackageDownload"
LogCode="NU1505"
MSBuildProjectFullPath="$(MSBuildProjectFullPath)"
TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
WarningsAsErrors="$(WarningsAsErrors)"
NoWarn="$(NoWarn)"
ContinueOnError="$(CollectPackageDownloadsContinueOnError)"
>
<Output TaskParameter="DeduplicatedItems" ItemName="DeduplicatedPackageDownloads" />
</CheckForDuplicateNuGetItemsTask>

<ItemGroup Condition="'@(DeduplicatedPackageDownloads)' != ''">
<PackageDownload Remove="@(PackageDownload)" />
<PackageDownload Include="@(DeduplicatedPackageDownloads)" />
</ItemGroup>
</Target>

<!--
============================================================
Expand Down Expand Up @@ -1298,7 +1377,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_ValidProjectsForRestore Include="$(MSBuildProjectFullPath)" />
</ItemGroup>
</Target>

<!--
============================================================
Import NuGet.RestoreEx.targets if the MSBuild property 'RestoreEnableStaticGraph'
Expand Down
9 changes: 9 additions & 0 deletions src/NuGet.Core/NuGet.Build.Tasks/Strings.Designer.cs

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

3 changes: 3 additions & 0 deletions src/NuGet.Core/NuGet.Build.Tasks/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,7 @@
<data name="Log_NoProjectsForRestore" xml:space="preserve">
<value>The solution did not have any projects to restore, ensure that all projects are known to be MSBuild and that the projects exist.</value>
</data>
<data name="Error_DuplicateItems" xml:space="preserve">
<value>Duplicate '{0}' items found. Remove the duplicate items or use the Update functionality to ensure a consistent restore behavior. The duplicate '{0}' items are: {1}.</value>
</data>
</root>
15 changes: 15 additions & 0 deletions src/NuGet.Core/NuGet.Common/Errors/NuGetLogCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,21 @@ public enum NuGetLogCode
/// </summary>
NU1503 = 1503,

/// <summary>
/// Duplicate PackageReference found
/// </summary>
NU1504 = 1504,

/// <summary>
/// Duplicate PackageDownload found
/// </summary>
NU1505 = 1505,

/// <summary>
/// Duplicate PackageVersion found
/// </summary>
NU1506 = 1506,

/// <summary>
/// Central package management is in use but there are multiple feeds configured without using package source mapping.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ NuGet.Common.IPackLogMessage.LibraryId.get -> string
NuGet.Common.IPackLogMessage.LibraryId.set -> void
NuGet.Common.NuGetLogCode.NU1013 = 1013 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1301 = 1301 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1504 = 1504 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1505 = 1505 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1506 = 1506 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1507 = 1507 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU5133 = 5133 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1802 = 1802 -> NuGet.Common.NuGetLogCode
NuGet.Common.PackagingLogMessage.Framework.get -> NuGet.Frameworks.NuGetFramework
NuGet.Common.PackagingLogMessage.Framework.set -> void
NuGet.Common.PackagingLogMessage.LibraryId.get -> string
NuGet.Common.PackagingLogMessage.LibraryId.set -> void
static NuGet.Common.PackagingLogMessage.CreateWarning(string message, NuGet.Common.NuGetLogCode code, string libraryId, NuGet.Frameworks.NuGetFramework framework) -> NuGet.Common.PackagingLogMessage
static NuGet.Common.PackagingLogMessage.CreateWarning(string message, NuGet.Common.NuGetLogCode code, string libraryId, NuGet.Frameworks.NuGetFramework framework) -> NuGet.Common.PackagingLogMessage
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ NuGet.Common.IPackLogMessage.LibraryId.get -> string
NuGet.Common.IPackLogMessage.LibraryId.set -> void
NuGet.Common.NuGetLogCode.NU1013 = 1013 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1301 = 1301 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1504 = 1504 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1505 = 1505 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1506 = 1506 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1507 = 1507 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU5133 = 5133 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1802 = 1802 -> NuGet.Common.NuGetLogCode
NuGet.Common.PackagingLogMessage.Framework.get -> NuGet.Frameworks.NuGetFramework
NuGet.Common.PackagingLogMessage.Framework.set -> void
NuGet.Common.PackagingLogMessage.LibraryId.get -> string
NuGet.Common.PackagingLogMessage.LibraryId.set -> void
static NuGet.Common.PackagingLogMessage.CreateWarning(string message, NuGet.Common.NuGetLogCode code, string libraryId, NuGet.Frameworks.NuGetFramework framework) -> NuGet.Common.PackagingLogMessage
static NuGet.Common.PackagingLogMessage.CreateWarning(string message, NuGet.Common.NuGetLogCode code, string libraryId, NuGet.Frameworks.NuGetFramework framework) -> NuGet.Common.PackagingLogMessage
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ NuGet.Common.IPackLogMessage.LibraryId.get -> string
NuGet.Common.IPackLogMessage.LibraryId.set -> void
NuGet.Common.NuGetLogCode.NU1013 = 1013 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1301 = 1301 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1504 = 1504 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1505 = 1505 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1506 = 1506 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1507 = 1507 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU5133 = 5133 -> NuGet.Common.NuGetLogCode
NuGet.Common.NuGetLogCode.NU1802 = 1802 -> NuGet.Common.NuGetLogCode
NuGet.Common.PackagingLogMessage.Framework.get -> NuGet.Frameworks.NuGetFramework
NuGet.Common.PackagingLogMessage.Framework.set -> void
NuGet.Common.PackagingLogMessage.LibraryId.get -> string
NuGet.Common.PackagingLogMessage.LibraryId.set -> void
static NuGet.Common.PackagingLogMessage.CreateWarning(string message, NuGet.Common.NuGetLogCode code, string libraryId, NuGet.Frameworks.NuGetFramework framework) -> NuGet.Common.PackagingLogMessage
static NuGet.Common.PackagingLogMessage.CreateWarning(string message, NuGet.Common.NuGetLogCode code, string libraryId, NuGet.Frameworks.NuGetFramework framework) -> NuGet.Common.PackagingLogMessage
Loading

0 comments on commit a1afdef

Please sign in to comment.