diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/CheckForDuplicateItems.cs b/src/Tasks/Microsoft.NET.Build.Tasks/CheckForDuplicateItems.cs
new file mode 100644
index 000000000000..79cbf8c33d92
--- /dev/null
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/CheckForDuplicateItems.cs
@@ -0,0 +1,50 @@
+using Microsoft.Build.Framework;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace Microsoft.NET.Build.Tasks
+{
+ public class CheckForDuplicateItems : TaskBase
+ {
+ [Required]
+ public ITaskItem [] Items { get; set; }
+
+ [Required]
+ public string ItemName { get; set; }
+
+ public bool DefaultItemsEnabled { get; set; }
+
+ public bool DefaultItemsOfThisTypeEnabled { get; set; }
+
+ [Required]
+ public string PropertyNameToDisableDefaultItems { get; set; }
+
+ public string PropertyValueToDisableDefaultItems { get; set; } = "false";
+
+ [Required]
+ public string MoreInformationLink { get; set; }
+
+ protected override void ExecuteCore()
+ {
+ if (DefaultItemsEnabled && DefaultItemsOfThisTypeEnabled)
+ {
+ var duplicateItems = Items.GroupBy(i => i.ItemSpec).Where(g => g.Count() > 1).ToList();
+ if (duplicateItems.Any())
+ {
+ string duplicateItemsFormatted = string.Join("; ", duplicateItems.Select(d => $"'{d.Key}'"));
+
+ string message = string.Format(CultureInfo.CurrentCulture, Strings.DuplicateItemsError,
+ ItemName,
+ PropertyNameToDisableDefaultItems,
+ PropertyValueToDisableDefaultItems,
+ duplicateItemsFormatted);
+
+ Log.LogError(message);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj
index cf3869152262..3a3f411e6a7d 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj
@@ -35,6 +35,7 @@
+
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.Designer.cs b/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.Designer.cs
index b6f0882a00ad..61239917c95a 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.Designer.cs
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.Designer.cs
@@ -196,6 +196,15 @@ internal static string DOTNET1017 {
}
}
+ ///
+ /// Looks up a localized string similar to Duplicate {0} items were included. The .NET SDK includes {0} items from your project directory by default. You can either remove these items from your project file, or set the '{1}' property to '{2}' if you want to explicitly include them in your project file. The duplicate items were: {3}.
+ ///
+ internal static string DuplicateItemsError {
+ get {
+ return ResourceManager.GetString("DuplicateItemsError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The preprocessor token '{0}' has been given more than one value. Choosing '{1}' as the value..
///
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.resx b/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.resx
index acc22ff8db16..aac2ad67d272 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.resx
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/Resources/Strings.resx
@@ -225,4 +225,7 @@
Dependency conflict. '{0}' expected '{1}' but received '{2}'
+
+ Duplicate '{0}' items were included. The .NET SDK includes '{0}' items from your project directory by default. You can either remove these items from your project file, or set the '{1}' property to '{2}' if you want to explicitly include them in your project file. The duplicate items were: {3}
+
\ No newline at end of file
diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets b/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets
index 113e61cf442b..0cc5ead9e063 100644
--- a/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets
+++ b/src/Tasks/Microsoft.NET.Build.Tasks/build/Microsoft.NET.Sdk.DefaultItems.targets
@@ -52,4 +52,42 @@ Copyright (c) .NET Foundation. All rights reserved.
+
+
+
+
+
+ https://aka.ms/sdkimplicititems
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs b/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs
index 3f0cb0941d1b..bf911974bf3e 100644
--- a/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs
+++ b/test/Microsoft.NET.Build.Tests/GivenThereAreDefaultItems.cs
@@ -14,6 +14,7 @@
using System.Xml.Linq;
using Xunit;
using static Microsoft.NET.TestFramework.Commands.MSBuildTest;
+using Microsoft.NET.TestFramework.ProjectConstruction;
namespace Microsoft.NET.Build.Tests
{
@@ -352,6 +353,82 @@ public void Compile_items_can_be_explicitly_specified_while_default_EmbeddedReso
GivenThatWeWantAllResourcesInSatellite.TestSatelliteResources(_testAssetsManager, projectChanges, setup, "ExplicitCompileDefaultEmbeddedResource");
}
+ [Fact]
+ public void It_gives_an_error_message_if_duplicate_compile_items_are_included()
+ {
+ var testProject = new TestProject()
+ {
+ Name = "DuplicateCompileItems",
+ TargetFrameworks = "netstandard1.6",
+ IsSdkProject = true
+ };
+
+ var testAsset = _testAssetsManager.CreateTestProject(testProject)
+ .WithProjectChanges(project =>
+ {
+ var ns = project.Root.Name.Namespace;
+ var itemGroup = new XElement(ns + "ItemGroup");
+ project.Root.Add(itemGroup);
+ itemGroup.Add(new XElement(ns + "Compile", new XAttribute("Include", @"**\*.cs")));
+ })
+ .Restore(testProject.Name);
+
+ var buildCommand = new BuildCommand(Stage0MSBuild, Path.Combine(testAsset.TestRoot, testProject.Name));
+
+ WriteFile(Path.Combine(buildCommand.ProjectRootPath, "Class1.cs"), "public class Class1 {}");
+
+ buildCommand
+ .CaptureStdOut()
+ .Execute()
+ .Should()
+ .Fail()
+ .And.HaveStdOutContaining("DuplicateCompileItems.cs")
+ .And.HaveStdOutContaining("Class1.cs")
+ .And.HaveStdOutContaining("EnableDefaultCompileItems");
+ }
+
+ [Fact]
+ public void It_gives_the_correct_error_if_duplicate_compile_items_are_included_and_default_items_are_disabled()
+ {
+ var testProject = new TestProject()
+ {
+ Name = "DuplicateCompileItems",
+ TargetFrameworks = "netstandard1.6",
+ IsSdkProject = true
+ };
+
+ var testAsset = _testAssetsManager.CreateTestProject(testProject, "DuplicateCompileItemsWithDefaultItemsDisabled")
+ .WithProjectChanges(project =>
+ {
+ var ns = project.Root.Name.Namespace;
+
+ project.Root.Element(ns + "PropertyGroup").Add(
+ new XElement(ns + "EnableDefaultCompileItems", "false"));
+
+ var itemGroup = new XElement(ns + "ItemGroup");
+ project.Root.Add(itemGroup);
+ itemGroup.Add(new XElement(ns + "Compile", new XAttribute("Include", @"**\*.cs")));
+ itemGroup.Add(new XElement(ns + "Compile", new XAttribute("Include", @"DuplicateCompileItems.cs")));
+ })
+ .Restore(testProject.Name);
+
+ var buildCommand = new BuildCommand(Stage0MSBuild, Path.Combine(testAsset.TestRoot, testProject.Name));
+
+ WriteFile(Path.Combine(buildCommand.ProjectRootPath, "Class1.cs"), "public class Class1 {}");
+
+ buildCommand
+ .CaptureStdOut()
+ .Execute()
+ .Should()
+ .Fail()
+ .And.HaveStdOutContaining("DuplicateCompileItems.cs")
+ // Class1.cs wasn't included multiple times, so it shouldn't be mentioned
+ .And.NotHaveStdOutMatching("Class1.cs")
+ // Default items weren't enabled, so the error message should come from the C# compiler and shouldn't include the information about default compile items
+ .And.HaveStdOutContaining("MSB3105")
+ .And.NotHaveStdOutMatching("EnableDefaultCompileItems");
+ }
+
void RemoveGeneratedCompileItems(List compileItems)
{
// Remove auto-generated compile items.
diff --git a/test/Microsoft.NET.TestFramework/TestAsset.cs b/test/Microsoft.NET.TestFramework/TestAsset.cs
index 657b2e3d776b..a7d253194291 100644
--- a/test/Microsoft.NET.TestFramework/TestAsset.cs
+++ b/test/Microsoft.NET.TestFramework/TestAsset.cs
@@ -42,8 +42,7 @@ internal void FindProjectFiles()
{
_projectFiles = new List();
- var files = Directory.GetFiles(base.Path, "*.*", SearchOption.AllDirectories)
- .Where(file => !IsInBinOrObjFolder(file));
+ var files = Directory.GetFiles(base.Path, "*.*", SearchOption.AllDirectories);
foreach (string file in files)
{