diff --git a/src/Tasks/Common/ConflictResolution/ConflictItem.cs b/src/Tasks/Common/ConflictResolution/ConflictItem.cs index 06e616694f85..1c4c8bf6dfd9 100644 --- a/src/Tasks/Common/ConflictResolution/ConflictItem.cs +++ b/src/Tasks/Common/ConflictResolution/ConflictItem.cs @@ -19,7 +19,8 @@ internal enum ConflictItemType Reference, CopyLocal, Runtime, - Platform + Platform, + Analyzer } internal interface IConflictItem diff --git a/src/Tasks/Common/ConflictResolution/FrameworkListReader.cs b/src/Tasks/Common/ConflictResolution/FrameworkListReader.cs index ea61e33ad994..2e8a8d1d23aa 100644 --- a/src/Tasks/Common/ConflictResolution/FrameworkListReader.cs +++ b/src/Tasks/Common/ConflictResolution/FrameworkListReader.cs @@ -73,6 +73,13 @@ private static IEnumerable LoadConflictItems(string frameworkListP var ret = new List(); foreach (var file in frameworkList.Root.Elements("File")) { + var type = file.Attribute("Type")?.Value; + + if (type?.Equals("Analyzer", StringComparison.OrdinalIgnoreCase) ?? false) + { + continue; + } + var assemblyName = file.Attribute("AssemblyName")?.Value; var assemblyVersionString = file.Attribute("Version")?.Value; diff --git a/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs b/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs index b830b6dae75c..d7af2c763c07 100644 --- a/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs +++ b/src/Tasks/Common/ConflictResolution/ResolvePackageFileConflicts.cs @@ -13,12 +13,15 @@ namespace Microsoft.NET.Build.Tasks.ConflictResolution public class ResolvePackageFileConflicts : TaskWithAssemblyResolveHooks { private HashSet referenceConflicts = new HashSet(); + private HashSet analyzerConflicts = new HashSet(); private HashSet copyLocalConflicts = new HashSet(); private HashSet compilePlatformWinners = new HashSet(); private HashSet allConflicts = new HashSet(); public ITaskItem[] References { get; set; } + public ITaskItem[] Analyzers { get; set; } + public ITaskItem[] ReferenceCopyLocalPaths { get; set; } public ITaskItem[] OtherRuntimeItems { get; set; } @@ -46,6 +49,10 @@ public class ResolvePackageFileConflicts : TaskWithAssemblyResolveHooks [Output] public ITaskItem[] ReferencesWithoutConflicts { get; set; } + [Output] + public ITaskItem[] AnalyzersWithoutConflicts { get; set; } + + [Output] public ITaskItem[] ReferenceCopyLocalPathsWithoutConflicts { get; set; } @@ -90,6 +97,14 @@ protected override void ExecuteCore() // Remove platform items which won a conflict with a reference but subsequently lost to something else compilePlatformWinners.ExceptWith(allConflicts); + // resolve analyzer conflicts + var analyzerItems = GetConflictTaskItems(Analyzers, ConflictItemType.Analyzer).ToArray(); + + using (var analyzerConflictScope = new ConflictResolver(packageRanks, packageOverrides, log)) + { + analyzerConflictScope.ResolveConflicts(analyzerItems, ci => ci.FileName, HandleAnalyzerConflict); + } + // resolve conflicts that clash in output IEnumerable copyLocalItems; IEnumerable otherRuntimeItems; @@ -140,6 +155,7 @@ protected override void ExecuteCore() } ReferencesWithoutConflicts = RemoveConflicts(References, referenceConflicts); + AnalyzersWithoutConflicts = RemoveConflicts(Analyzers, analyzerConflicts); ReferenceCopyLocalPathsWithoutConflicts = RemoveConflicts(ReferenceCopyLocalPaths, copyLocalConflicts); Conflicts = CreateConflictTaskItems(allConflicts); @@ -233,6 +249,12 @@ private void HandleCompileConflict(ConflictItem winner, ConflictItem loser) allConflicts.Add(loser); } + private void HandleAnalyzerConflict(ConflictItem winner, ConflictItem loser) + { + analyzerConflicts.Add(loser.OriginalItem); + allConflicts.Add(loser); + } + private void HandleRuntimeConflict(ConflictItem winner, ConflictItem loser) { if (loser.ItemType == ConflictItemType.Reference) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ResolveTargetingPackAssetsTests.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ResolveTargetingPackAssetsTests.cs index 2acc9a23df23..8f1bc654175f 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ResolveTargetingPackAssetsTests.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/ResolveTargetingPackAssetsTests.cs @@ -41,13 +41,23 @@ public void Given_ResolvedTargetingPacks_with_valid_PATH_in_PlatformManifest_It_ }) }; + task.ProjectLanguage = "C#"; + task.Execute().Should().BeTrue(); task.ReferencesToAdd[0].ItemSpec.Should().Be(Path.Combine(mockPackageDirectory, "lib/Microsoft.Windows.SDK.NET.dll")); task.PlatformManifests[0].ItemSpec.Should().Be(Path.Combine(mockPackageDirectory, $"data{Path.DirectorySeparatorChar}PlatformManifest.txt")); + task.AnalyzersToAdd.Length.Should().Be(2); + task.AnalyzersToAdd[0].ItemSpec.Should().Be(Path.Combine(mockPackageDirectory, "analyzers/dotnet/anyAnalyzer.dll")); + task.AnalyzersToAdd[1].ItemSpec.Should().Be(Path.Combine(mockPackageDirectory, "analyzers/dotnet/cs/csAnalyzer.dll")); } private readonly string _frameworkList = - " "; +@" + + + + +"; } } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ResolveTargetingPackAssets.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ResolveTargetingPackAssets.cs index dae9757ffc04..9e7a02c5d043 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ResolveTargetingPackAssets.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ResolveTargetingPackAssets.cs @@ -26,9 +26,14 @@ public class ResolveTargetingPackAssets : TaskBase public string NetCoreTargetingPackRoot { get; set; } + public string ProjectLanguage { get; set; } + [Output] public ITaskItem[] ReferencesToAdd { get; set; } + [Output] + public ITaskItem[] AnalyzersToAdd { get; set; } + [Output] public ITaskItem[] PlatformManifests { get; set; } @@ -48,6 +53,7 @@ public ResolveTargetingPackAssets() protected override void ExecuteCore() { List referencesToAdd = new List(); + List analyzersToAdd = new List(); List platformManifests = new List(); PackageConflictPreferredPackages = string.Empty; List packageConflictOverrides = new List(); @@ -123,8 +129,8 @@ protected override void ExecuteCore() string frameworkListPath = Path.Combine(targetingPackDataPath, "FrameworkList.xml"); - AddReferencesFromFrameworkList(frameworkListPath, targetingPackRoot, targetingPackDllFolder, - targetingPack, referencesToAdd); + AddItemsFromFrameworkList(frameworkListPath, targetingPackRoot, targetingPackDllFolder, + targetingPack, referencesToAdd, analyzersToAdd); if (File.Exists(platformManifestPath)) { @@ -148,7 +154,10 @@ protected override void ExecuteCore() // Filter out duplicate references (which can happen when referencing two different profiles that overlap) List deduplicatedReferences = DeduplicateItems(referencesToAdd); - ReferencesToAdd = deduplicatedReferences.Distinct() .ToArray(); + ReferencesToAdd = deduplicatedReferences.Distinct().ToArray(); + + List deduplicatedAnalyzers = DeduplicateItems(analyzersToAdd); + AnalyzersToAdd = deduplicatedAnalyzers.Distinct().ToArray(); PlatformManifests = platformManifests.ToArray(); PackageConflictOverrides = packageConflictOverrides.ToArray(); @@ -184,7 +193,7 @@ private void AddNetStandardTargetingPackAssets(ITaskItem targetingPack, string t foreach (var dll in Directory.GetFiles(targetingPackAssetPath, "*.dll")) { - var reference = CreateReferenceItem(dll, targetingPack); + var reference = CreateItem(dll, targetingPack); if (!Path.GetFileName(dll).Equals("netstandard.dll", StringComparison.OrdinalIgnoreCase)) { @@ -195,9 +204,9 @@ private void AddNetStandardTargetingPackAssets(ITaskItem targetingPack, string t } } - private void AddReferencesFromFrameworkList(string frameworkListPath, string targetingPackRoot, + private void AddItemsFromFrameworkList(string frameworkListPath, string targetingPackRoot, string targetingPackDllFolder, - ITaskItem targetingPack, List referenceItems) + ITaskItem targetingPack, List referenceItems, List analyzerItems) { XDocument frameworkListDoc = XDocument.Load(frameworkListPath); @@ -234,17 +243,40 @@ private void AddReferencesFromFrameworkList(string frameworkListPath, string tar continue; } - string dllPath = usePathElementsInFrameworkListAsFallBack ? + string itemType = fileElement.Attribute("Type")?.Value; + bool isAnalyzer = itemType?.Equals("Analyzer", StringComparison.OrdinalIgnoreCase) ?? false; + + string dllPath = usePathElementsInFrameworkListAsFallBack || isAnalyzer ? Path.Combine(targetingPackRoot, fileElement.Attribute("Path").Value) : GetDllPathViaAssemblyName(targetingPackDllFolder, fileElement); - var referenceItem = CreateReferenceItem(dllPath, targetingPack); + var item = CreateItem(dllPath, targetingPack); - referenceItem.SetMetadata("AssemblyVersion", fileElement.Attribute("AssemblyVersion").Value); - referenceItem.SetMetadata("FileVersion", fileElement.Attribute("FileVersion").Value); - referenceItem.SetMetadata("PublicKeyToken", fileElement.Attribute("PublicKeyToken").Value); + item.SetMetadata("AssemblyVersion", fileElement.Attribute("AssemblyVersion").Value); + item.SetMetadata("FileVersion", fileElement.Attribute("FileVersion").Value); + item.SetMetadata("PublicKeyToken", fileElement.Attribute("PublicKeyToken").Value); + + if (isAnalyzer) + { + string itemLanguage = fileElement.Attribute("Language")?.Value; + + if (itemLanguage != null) + { + // expect cs instead of C#, fs rather than F# per NuGet conventions + string projectLanguage = ProjectLanguage?.Replace('#', 's'); - referenceItems.Add(referenceItem); + if (projectLanguage == null || !projectLanguage.Equals(itemLanguage, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + } + + analyzerItems.Add(item); + } + else + { + referenceItems.Add(item); + } } } @@ -279,7 +311,7 @@ private static string GetDllPathViaAssemblyName(string targetingPackDllFolder, X return dllPath; } - private TaskItem CreateReferenceItem(string dll, ITaskItem targetingPack) + private TaskItem CreateItem(string dll, ITaskItem targetingPack) { var reference = new TaskItem(dll); diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ConflictResolution.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ConflictResolution.targets index 09db05dd3f7d..78169f96cace 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ConflictResolution.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ConflictResolution.targets @@ -38,6 +38,7 @@ Copyright (c) .NET Foundation. All rights reserved. + @@ -56,6 +58,8 @@ Copyright (c) .NET Foundation. All rights reserved. + + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets index 0ade634ff93a..f6810481f02f 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets @@ -326,12 +326,14 @@ Copyright (c) .NET Foundation. All rights reserved. + diff --git a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs index 5d31612fab8a..d2e33c1b5cb9 100644 --- a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs +++ b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToResolveConflicts.cs @@ -226,5 +226,36 @@ public void FilesFromAspNetCoreSharedFrameworkAreNotIncluded() outputDirectory.Should().NotHaveFile("Microsoft.Extensions.DependencyInjection.Abstractions.dll"); } + + [Fact] + public void AnalyzersAreConflictResolved() + { + var testProject = new TestProject() + { + Name = nameof(AnalyzersAreConflictResolved), + TargetFrameworks = "net5.0" + }; + + // add the package referenced analyzers + testProject.PackageReferences.Add(new TestPackageReference("Microsoft.CodeAnalysis.NetAnalyzers", "5.0.3")); + + // enable inbox analyzers too + var testAsset = _testAssetsManager.CreateTestProject(testProject) + .WithProjectChanges(project => + { + var ns = project.Root.Name.Namespace; + var itemGroup = new XElement(ns + "PropertyGroup"); + project.Root.Add(itemGroup); + itemGroup.Add(new XElement(ns + "EnableNETAnalyzers", "true")); + itemGroup.Add(new XElement(ns + "TreatWarningsAsErrors", "true")); + }); + + var buildCommand = new BuildCommand(testAsset); + + buildCommand + .Execute() + .Should() + .Pass(); + } } }