From faf3d4e02ad331da3a178521f6dddeb4769a1d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borja=20Dom=C3=ADnguez?= Date: Sat, 30 Sep 2023 08:33:51 +0200 Subject: [PATCH] Properly filter assemblies compatible with the Roslyn version supported by Unity (#255) * Properly filter assemblies compatible with the Roslyn version supported by Unity * WIP * WIP --- src/UnityNuGet.Tests/NuGetHelperTests.cs | 49 +++++++++++++++++ src/UnityNuGet/NuGetHelper.cs | 67 +++++++++++++++++++++++- src/UnityNuGet/RegistryCache.cs | 35 ++++++++----- 3 files changed, 137 insertions(+), 14 deletions(-) diff --git a/src/UnityNuGet.Tests/NuGetHelperTests.cs b/src/UnityNuGet.Tests/NuGetHelperTests.cs index e975f7e4..44329295 100644 --- a/src/UnityNuGet.Tests/NuGetHelperTests.cs +++ b/src/UnityNuGet.Tests/NuGetHelperTests.cs @@ -10,6 +10,55 @@ namespace UnityNuGet.Tests { public class NuGetHelperTests { + [Test] + [TestCase("analyzers/dotnet/roslyn3.8/cs/Test.resources.dll")] + [TestCase("analyzers/dotnet/roslyn3.8/Test.resources.dll")] + [TestCase("analyzers/dotnet/cs/Test.resources.dll")] + [TestCase("analyzers/dotnet/Test.resources.dll")] + [TestCase("analyzers/Test.resources.dll")] + public void IsApplicableAnalyzerResource_Valid(string input) + { + Assert.True(NuGetHelper.IsApplicableAnalyzerResource(input)); + } + + [Test] + [TestCase("analyzers/dotnet/roslyn3.8/vb/cs/Test.resources.dll")] + [TestCase("analyzers/dotnet/roslyn3.8/cs/Test.dll")] + [TestCase("analyzers/dotnet/roslyn3.8/Test.dll")] + [TestCase("analyzers/dotnet/vb/Test.dll")] + [TestCase("analyzers/dotnet/cs/Test.dll")] + [TestCase("analyzers/dotnet/Test.dll")] + [TestCase("analyzers/Test.dll")] + public void IsApplicableAnalyzerResource_Invalid(string input) + { + Assert.False(NuGetHelper.IsApplicableAnalyzerResource(input)); + } + + // Examples: + // Meziantou.Analyzer -> analyzers/dotnet/roslyn3.8/cs/* + // Microsoft.Unity.Analyzers -> analyzers/dotnet/cs/* + // Microsoft.VisualStudio.Threading.Analyzers -> analyzers/cs/* + // SonarAnalyzer.CSharp -> analyzers/* + // StrongInject -> analyzers/dotnet/cs/* + analyzers/dotnet/roslyn3.8/cs/* + [Test] + [TestCase("analyzers/dotnet/roslyn3.8/cs/Test.dll")] + [TestCase("analyzers/dotnet/roslyn3.8/Test.dll")] + [TestCase("analyzers/dotnet/cs/Test.dll")] + [TestCase("analyzers/dotnet/Test.dll")] + [TestCase("analyzers/Test.dll")] + public void IsApplicableUnitySupportedRoslynVersionFolder_Valid(string input) + { + Assert.True(NuGetHelper.IsApplicableUnitySupportedRoslynVersionFolder(input)); + } + + [Test] + [TestCase("analyzers/dotnet/roslyn4.0/cs/Test.dll")] + [TestCase("analyzers/dotnet/roslyn4.0/Test.dll")] + public void IsApplicableUnitySupportedRoslynVersionFolder_Invalid(string input) + { + Assert.False(NuGetHelper.IsApplicableUnitySupportedRoslynVersionFolder(input)); + } + [Test] public void GetCompatiblePackageDependencyGroups_SpecificSingleFramework() { diff --git a/src/UnityNuGet/NuGetHelper.cs b/src/UnityNuGet/NuGetHelper.cs index 42fe974e..7266c3d0 100644 --- a/src/UnityNuGet/NuGetHelper.cs +++ b/src/UnityNuGet/NuGetHelper.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.Protocol.Core.Types; @@ -8,6 +10,69 @@ namespace UnityNuGet { static class NuGetHelper { + // https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support + private static readonly Regex roslynVersionRegex = new(@"/roslyn(\d+)\.(\d+)\.?(\d*)/"); + + // https://docs.unity3d.com/Manual/roslyn-analyzers.html + private static readonly Version unityRoslynSupportedVersion = new(3, 8, 0); + + // https://github.com/dotnet/sdk/blob/2838d93742658300698b2194882d57fd978fb168/src/Tasks/Microsoft.NET.Build.Tasks/NuGetUtils.NuGet.cs#L50 + public static bool IsApplicableAnalyzer(string file) => IsApplicableAnalyzer(file, "C#"); + + private static bool IsApplicableAnalyzer(string file, string projectLanguage) + { + // This logic is preserved from previous implementations. + // See https://github.com/NuGet/Home/issues/6279#issuecomment-353696160 for possible issues with it. + bool IsAnalyzer() + { + return file.StartsWith("analyzers", StringComparison.Ordinal) + && file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) + && !file.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase); + } + + bool CS() => file.Contains("/cs/", StringComparison.OrdinalIgnoreCase); + bool VB() => file.Contains("/vb/", StringComparison.OrdinalIgnoreCase); + + bool FileMatchesProjectLanguage() + { + return projectLanguage switch + { + "C#" => CS() || !VB(), + "VB" => VB() || !CS(), + _ => false, + }; + } + + return IsAnalyzer() && FileMatchesProjectLanguage(); + } + + public static bool IsApplicableAnalyzerResource(string file) + { + bool IsResource() + { + return file.StartsWith("analyzers", StringComparison.Ordinal) + && file.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase); + } + + bool CS() => file.Contains("/cs/", StringComparison.OrdinalIgnoreCase); + bool VB() => file.Contains("/vb/", StringComparison.OrdinalIgnoreCase); + + // Czech locale is cs, catch /vb/cs/ + return IsResource() && ((!CS() && !VB()) || (CS() && !VB())); + } + + public static bool IsApplicableUnitySupportedRoslynVersionFolder(string file) + { + var roslynVersionMatch = roslynVersionRegex.Match(file); + + bool hasRoslynVersionFolder = roslynVersionMatch.Success; + bool hasUnitySupportedRoslynVersionFolder = hasRoslynVersionFolder && + int.Parse(roslynVersionMatch.Groups[1].Value) == unityRoslynSupportedVersion.Major && + int.Parse(roslynVersionMatch.Groups[2].Value) == unityRoslynSupportedVersion.Minor; + + return !hasRoslynVersionFolder || hasUnitySupportedRoslynVersionFolder; + } + public static IEnumerable<(FrameworkSpecificGroup, RegistryTargetFramework)> GetClosestFrameworkSpecificGroups(IEnumerable versions, IEnumerable targetFrameworks) { var result = new List<(FrameworkSpecificGroup, RegistryTargetFramework)>(); diff --git a/src/UnityNuGet/RegistryCache.cs b/src/UnityNuGet/RegistryCache.cs index 1abfadb6..c63f4b3a 100644 --- a/src/UnityNuGet/RegistryCache.cs +++ b/src/UnityNuGet/RegistryCache.cs @@ -33,7 +33,7 @@ public class RegistryCache public static readonly bool IsRunningOnAzure = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME")); // Change this version number if the content of the packages are changed by an update of this class - private const string CurrentRegistryVersion = "1.7.0"; + private const string CurrentRegistryVersion = "1.8.0"; private static readonly Encoding Utf8EncodingNoBom = new UTF8Encoding(false, false); private readonly string _rootPersistentFolder; @@ -260,7 +260,6 @@ private async Task BuildInternal() var currentVersion = packageIdentity.Version; string npmCurrentVersion = GetNpmVersion(currentVersion); - if (packageEntry.Version == null || !packageEntry.Version.Satisfies(packageMeta.Identity.Version)) { continue; @@ -596,12 +595,11 @@ RegistryEntry packageEntry { var packageFiles = await packageReader.GetItemsAsync(PackagingConstants.Folders.Analyzers, CancellationToken.None); - var analyzerFiles = packageFiles.SelectMany(p => p.Items).Where(p => p.StartsWith("analyzers/dotnet/cs")).ToArray(); - - if (analyzerFiles.Length == 0) - { - analyzerFiles = packageFiles.SelectMany(p => p.Items).Where(p => p.StartsWith("analyzers")).ToArray(); - } + // https://learn.microsoft.com/en-us/nuget/guides/analyzers-conventions#analyzers-path-format + var analyzerFiles = packageFiles + .SelectMany(p => p.Items) + .Where(p => NuGetHelper.IsApplicableUnitySupportedRoslynVersionFolder(p) && (NuGetHelper.IsApplicableAnalyzer(p) || NuGetHelper.IsApplicableAnalyzerResource(p))) + .ToArray(); var createdDirectoryList = new List(); @@ -640,11 +638,22 @@ RegistryEntry packageEntry if (fileExtension == ".dll") { - meta = UnityMeta.GetMetaForDll( - GetStableGuid(identity, fileInUnityPackage), - new PlatformDefinition(UnityOs.AnyOs, UnityCpu.None, isEditorConfig: false), - new string[] { "RoslynAnalyzer" }, - Array.Empty()); + if (NuGetHelper.IsApplicableAnalyzer(analyzerFile)) + { + meta = UnityMeta.GetMetaForDll( + GetStableGuid(identity, fileInUnityPackage), + new PlatformDefinition(UnityOs.AnyOs, UnityCpu.None, isEditorConfig: false), + new string[] { "RoslynAnalyzer" }, + Array.Empty()); + } + else + { + meta = UnityMeta.GetMetaForDll( + GetStableGuid(identity, fileInUnityPackage), + new PlatformDefinition(UnityOs.AnyOs, UnityCpu.None, isEditorConfig: false), + Array.Empty(), + Array.Empty()); + } } else {