diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2e3b4ae1f740..05a1c8ee4e04 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -66,6 +66,8 @@
+
+
diff --git a/NuGet.config b/NuGet.config
index ecf2c176d5ad..e31293220294 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -25,9 +25,12 @@
+
+
+
diff --git a/eng/Signing.props b/eng/Signing.props
index d21cce9aa953..bf15cbf9652e 100644
--- a/eng/Signing.props
+++ b/eng/Signing.props
@@ -67,7 +67,6 @@
-
@@ -81,6 +80,10 @@
+
+
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 79094c4a7c9a..9b3319e19e2d 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -97,43 +97,43 @@
63a09289745da5c256e7baf5f4194a750b1ec878
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
-
+
https://github.com/dotnet/roslyn
- 6b364f666a4014bc8269131ef30df18c16a83511
+ 889d6fefa9e7ffb51f97ee1628856d27697c8fc9
https://github.com/dotnet/aspnetcore
diff --git a/eng/Versions.props b/eng/Versions.props
index ab1f888b35cb..d037f6439041 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -26,6 +26,7 @@
compiler API targeted by analyzer assemblies. This is mostly an issue on source-build as
in that build mode analyzer assemblies always target the live compiler API. -->
true
+ true
6.0.1
true
1.6.0-preview.25064.5
@@ -213,14 +214,14 @@
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
- 4.14.0-1.25064.9
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
+ 4.14.0-1.25065.2
diff --git a/sdk.sln b/sdk.sln
index 2480a90d380b..eb92cfa5f652 100644
--- a/sdk.sln
+++ b/sdk.sln
@@ -517,6 +517,10 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.DotNet.HotReload.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.HotReload.Agent.Package", "src\BuiltInTools\HotReloadAgent\Microsoft.DotNet.HotReload.Agent.Package.csproj", "{2FF79F82-60C1-349A-4726-7783D5A6D5DF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Sdk.AnalyzerRedirecting", "src\Microsoft.Net.Sdk.AnalyzerRedirecting\Microsoft.Net.Sdk.AnalyzerRedirecting.csproj", "{27BBE29B-CE6F-401F-B3CF-B07DC556FAD1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Sdk.AnalyzerRedirecting.Tests", "test\Microsoft.Net.Sdk.AnalyzerRedirecting.Tests\Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj", "{234BE46E-4AD6-485C-B0D3-E704EF504312}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -979,6 +983,14 @@ Global
{2FF79F82-60C1-349A-4726-7783D5A6D5DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FF79F82-60C1-349A-4726-7783D5A6D5DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FF79F82-60C1-349A-4726-7783D5A6D5DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27BBE29B-CE6F-401F-B3CF-B07DC556FAD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27BBE29B-CE6F-401F-B3CF-B07DC556FAD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27BBE29B-CE6F-401F-B3CF-B07DC556FAD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27BBE29B-CE6F-401F-B3CF-B07DC556FAD1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {234BE46E-4AD6-485C-B0D3-E704EF504312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {234BE46E-4AD6-485C-B0D3-E704EF504312}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {234BE46E-4AD6-485C-B0D3-E704EF504312}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {234BE46E-4AD6-485C-B0D3-E704EF504312}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1159,6 +1171,8 @@ Global
{8D6A9984-118D-4415-A8FA-AB1F26CF5C44} = {3FA6F1CB-295B-4414-B18F-93845917A8CD}
{418B10BD-CA42-49F3-8F4A-D8CC90C8A17D} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91}
{2FF79F82-60C1-349A-4726-7783D5A6D5DF} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91}
+ {27BBE29B-CE6F-401F-B3CF-B07DC556FAD1} = {22AB674F-ED91-4FBC-BFEE-8A1E82F9F05E}
+ {234BE46E-4AD6-485C-B0D3-E704EF504312} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6}
diff --git a/src/Installer/redist-installer/packaging/windows/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.nuspec b/src/Installer/redist-installer/packaging/windows/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.nuspec
new file mode 100644
index 000000000000..b3a7b71cd697
--- /dev/null
+++ b/src/Installer/redist-installer/packaging/windows/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.nuspec
@@ -0,0 +1,17 @@
+
+
+ VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers
+ 1.0.0
+ VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers
+ Microsoft
+ Microsoft
+ https://www.microsoft.com/net/dotnet_library_license.htm
+ https://github.com/dotnet/sdk
+ true
+ Analyzers and generators from the runtime and SDK for VS insertion
+ © Microsoft Corporation. All rights reserved.
+
+
+
+
+
diff --git a/src/Installer/redist-installer/redist-installer.csproj b/src/Installer/redist-installer/redist-installer.csproj
index cf099e1fb6d4..3cfdbb9a7cf1 100644
--- a/src/Installer/redist-installer/redist-installer.csproj
+++ b/src/Installer/redist-installer/redist-installer.csproj
@@ -36,6 +36,7 @@
+
diff --git a/src/Installer/redist-installer/targets/GenerateMSIs.targets b/src/Installer/redist-installer/targets/GenerateMSIs.targets
index 8e024506a0d8..727292961994 100644
--- a/src/Installer/redist-installer/targets/GenerateMSIs.targets
+++ b/src/Installer/redist-installer/targets/GenerateMSIs.targets
@@ -27,6 +27,10 @@
$(SdkPkgSourcesRootDirectory)/VS.Redist.Common.NetCore.SdkPlaceholder.nuspec
$(ArtifactsNonShippingPackagesDir)VS.Redist.Common.NetCore.SdkPlaceholder.$(Architecture).$(FullNugetVersion).nupkg
+ $(SdkPkgSourcesRootDirectory)/VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.nuspec
+ $(ArtifactsNonShippingPackagesDir)VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.$(FullNugetVersion).nupkg
+ $(ArtifactsNonShippingPackagesDir)VS.Redist.Common.Net.Core.SDK.RuntimeAnalyzers.swr
+
$(ArtifactsObjDir)/LightCommandPackages
@@ -414,6 +418,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Installer/redist-installer/targets/GenerateRuntimeAnalyzers.targets b/src/Installer/redist-installer/targets/GenerateRuntimeAnalyzers.targets
new file mode 100644
index 000000000000..e84a36f39ce9
--- /dev/null
+++ b/src/Installer/redist-installer/targets/GenerateRuntimeAnalyzers.targets
@@ -0,0 +1,7 @@
+
+
+
+ $(ArtifactsBinDir)$(Configuration)\RuntimeAnalyzers
+
+
+
diff --git a/src/Layout/redist/redist.csproj b/src/Layout/redist/redist.csproj
index a241ed598f77..12299134dd66 100644
--- a/src/Layout/redist/redist.csproj
+++ b/src/Layout/redist/redist.csproj
@@ -27,6 +27,7 @@
+
diff --git a/src/Layout/redist/targets/GenerateLayout.targets b/src/Layout/redist/targets/GenerateLayout.targets
index 3160feb51095..f3c22c9cdf4e 100644
--- a/src/Layout/redist/targets/GenerateLayout.targets
+++ b/src/Layout/redist/targets/GenerateLayout.targets
@@ -131,6 +131,13 @@
+
+
+
+
diff --git a/src/Layout/redist/targets/RuntimeAnalyzers.targets b/src/Layout/redist/targets/RuntimeAnalyzers.targets
new file mode 100644
index 000000000000..093a527e5ff8
--- /dev/null
+++ b/src/Layout/redist/targets/RuntimeAnalyzers.targets
@@ -0,0 +1,24 @@
+
+
+
+ $(ArtifactsBinDir)redist\$(Configuration)\dotnet\
+ packs\Microsoft.NetCore.App.Ref\*\analyzers
+ packs\Microsoft.WindowsDesktop.App.Ref\*\analyzers
+ packs\Microsoft.AspNetCore.App.Ref\*\analyzers
+ sdk\*\Sdks\Microsoft.NET.Sdk\analyzers
+ sdk\*\Sdks\Microsoft.NET.Sdk.Web\analyzers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/AnalyzerRedirectingPackage.cs b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/AnalyzerRedirectingPackage.cs
new file mode 100644
index 000000000000..8a4a84e9fc81
--- /dev/null
+++ b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/AnalyzerRedirectingPackage.cs
@@ -0,0 +1,6 @@
+using Microsoft.VisualStudio.Shell;
+
+namespace Microsoft.Net.Sdk.AnalyzerRedirecting;
+
+[Guid("ef89a321-14da-4de4-8f71-9bf1feea15aa")]
+public sealed class AnalyzerRedirectingPackage : AsyncPackage;
diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/Microsoft.Net.Sdk.AnalyzerRedirecting.csproj b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/Microsoft.Net.Sdk.AnalyzerRedirecting.csproj
new file mode 100644
index 000000000000..0b05a215c785
--- /dev/null
+++ b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/Microsoft.Net.Sdk.AnalyzerRedirecting.csproj
@@ -0,0 +1,43 @@
+
+
+
+
+
+ net472
+ enable
+
+ false
+ true
+
+
+ RoslynDev
+ true
+ true
+ true
+ true
+ false
+ 42.42.42.4242424
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(VersionPrefix).$(VersionSuffixDateStamp)$(VersionSuffixBuildOfTheDayPadded)
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/SdkAnalyzerAssemblyRedirector.cs b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/SdkAnalyzerAssemblyRedirector.cs
new file mode 100644
index 000000000000..c81a829990c2
--- /dev/null
+++ b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/SdkAnalyzerAssemblyRedirector.cs
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+using Microsoft.CodeAnalysis.Workspaces.AnalyzerRedirecting;
+
+// Example:
+// FullPath: "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll"
+// ProductVersion: "8.0.8"
+// PathSuffix: "analyzers\dotnet"
+using AnalyzerInfo = (string FullPath, string ProductVersion, string PathSuffix);
+
+namespace Microsoft.Net.Sdk.AnalyzerRedirecting;
+
+[Export(typeof(IAnalyzerAssemblyRedirector))]
+public sealed class SdkAnalyzerAssemblyRedirector : IAnalyzerAssemblyRedirector
+{
+ private readonly string? _insertedAnalyzersDirectory;
+
+ ///
+ /// Map from analyzer assembly name (file name without extension) to a list of matching analyzers.
+ ///
+ private readonly ImmutableDictionary> _analyzerMap;
+
+ [ImportingConstructor]
+ public SdkAnalyzerAssemblyRedirector()
+ : this(Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\DotNetRuntimeAnalyzers"))) { }
+
+ // Internal for testing.
+ internal SdkAnalyzerAssemblyRedirector(string? insertedAnalyzersDirectory)
+ {
+ _insertedAnalyzersDirectory = insertedAnalyzersDirectory;
+ _analyzerMap = CreateAnalyzerMap();
+ }
+
+ private ImmutableDictionary> CreateAnalyzerMap()
+ {
+ var builder = ImmutableDictionary.CreateBuilder>(StringComparer.OrdinalIgnoreCase);
+
+ // Expects layout like:
+ // VsInstallDir\SDK\RuntimeAnalyzers\WindowsDesktopAnalyzers\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll
+ // ~~~~~~~~~~~~~~~~~~~~~~~ = topLevelDirectory
+ // ~~~~~ = versionDirectory
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ = analyzerPath
+
+ foreach (string topLevelDirectory in Directory.EnumerateDirectories(_insertedAnalyzersDirectory))
+ {
+ foreach (string versionDirectory in Directory.EnumerateDirectories(topLevelDirectory))
+ {
+ foreach (string analyzerPath in Directory.EnumerateFiles(versionDirectory, "*.dll", SearchOption.AllDirectories))
+ {
+ if (!analyzerPath.StartsWith(versionDirectory, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ string version = Path.GetFileName(versionDirectory);
+ string analyzerName = Path.GetFileNameWithoutExtension(analyzerPath);
+ string pathSuffix = analyzerPath.Substring(versionDirectory.Length + 1 /* slash */);
+ pathSuffix = Path.GetDirectoryName(pathSuffix);
+
+ AnalyzerInfo analyzer = new() { FullPath = analyzerPath, ProductVersion = version, PathSuffix = pathSuffix };
+
+ if (builder.TryGetValue(analyzerName, out var existing))
+ {
+ existing.Add(analyzer);
+ }
+ else
+ {
+ builder.Add(analyzerName, [analyzer]);
+ }
+ }
+ }
+ }
+
+ return builder.ToImmutable();
+ }
+
+ public string? RedirectPath(string fullPath)
+ {
+ if (_analyzerMap.TryGetValue(Path.GetFileNameWithoutExtension(fullPath), out var analyzers))
+ {
+ foreach (AnalyzerInfo analyzer in analyzers)
+ {
+ var directoryPath = Path.GetDirectoryName(fullPath);
+
+ // Note that both paths we compare here are normalized via netfx's Path.GetDirectoryName.
+ if (directoryPath.EndsWith(analyzer.PathSuffix, StringComparison.OrdinalIgnoreCase) &&
+ majorAndMinorVersionsMatch(directoryPath, analyzer.PathSuffix, analyzer.ProductVersion))
+ {
+ return analyzer.FullPath;
+ }
+ }
+ }
+
+ return null;
+
+ static bool majorAndMinorVersionsMatch(string directoryPath, string pathSuffix, string version)
+ {
+ // Find the version number in the directory path - it is in the directory name before the path suffix.
+ // Example:
+ // "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\" = directoryPath
+ // ~~~~~~~~~~~~~~~~ = pathSuffix
+ // ~~~~~ = directoryPathVersion
+ // This can match also a NuGet package because the version number is at the same position:
+ // "C:\.nuget\packages\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\"
+
+ int index = directoryPath.LastIndexOf(pathSuffix, StringComparison.OrdinalIgnoreCase);
+ if (index < 0)
+ {
+ return false;
+ }
+
+ string directoryPathVersion = Path.GetFileName(Path.GetDirectoryName(directoryPath.Substring(0, index)));
+
+ return areVersionMajorMinorPartEqual(directoryPathVersion, version);
+ }
+
+ static bool areVersionMajorMinorPartEqual(string version1, string version2)
+ {
+ int firstDotIndex = version1.IndexOf('.');
+ if (firstDotIndex < 0)
+ {
+ return false;
+ }
+
+ int secondDotIndex = version1.IndexOf('.', firstDotIndex + 1);
+ if (secondDotIndex < 0)
+ {
+ return false;
+ }
+
+ return 0 == string.Compare(version1, 0, version2, 0, secondDotIndex, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/Microsoft.Net.Sdk.AnalyzerRedirecting/source.extension.vsixmanifest b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/source.extension.vsixmanifest
new file mode 100644
index 000000000000..3c3150875aab
--- /dev/null
+++ b/src/Microsoft.Net.Sdk.AnalyzerRedirecting/source.extension.vsixmanifest
@@ -0,0 +1,23 @@
+
+
+
+
+ .NET SDK Analyzer Redirecting
+ .NET SDK Analyzer Redirecting Package.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ amd64
+
+
+
diff --git a/src/Tasks/sdk-tasks/GenerateRuntimeAnalyzersSWR.cs b/src/Tasks/sdk-tasks/GenerateRuntimeAnalyzersSWR.cs
new file mode 100644
index 000000000000..7131a5fc83dc
--- /dev/null
+++ b/src/Tasks/sdk-tasks/GenerateRuntimeAnalyzersSWR.cs
@@ -0,0 +1,134 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.DotNet.Cli.Build
+{
+ public class GenerateRuntimeAnalyzersSWR : Task
+ {
+ [Required]
+ public string RuntimeAnalyzersLayoutDirectory { get; set; }
+
+ [Required]
+ public string OutputFile { get; set; }
+
+ public override bool Execute()
+ {
+ StringBuilder sb = new StringBuilder(SWR_HEADER);
+
+ // NOTE: Keep in sync with SdkAnalyzerAssemblyRedirector.
+ // This is intentionally short to avoid long path problems.
+ const string installDir = @"DotNetRuntimeAnalyzers";
+
+ AddFolder(sb,
+ @"AnalyzerRedirecting",
+ @"Common7\IDE\CommonExtensions\Microsoft\AnalyzerRedirecting",
+ filesToInclude:
+ [
+ "Microsoft.Net.Sdk.AnalyzerRedirecting.dll",
+ "Microsoft.Net.Sdk.AnalyzerRedirecting.pkgdef",
+ "extension.vsixmanifest",
+ ]);
+
+ AddFolder(sb,
+ @"AspNetCoreAnalyzers",
+ @$"{installDir}\AspNetCoreAnalyzers");
+
+ AddFolder(sb,
+ @"NetCoreAnalyzers",
+ @$"{installDir}\NetCoreAnalyzers");
+
+ AddFolder(sb,
+ @"WindowsDesktopAnalyzers",
+ @$"{installDir}\WindowsDesktopAnalyzers");
+
+ AddFolder(sb,
+ @"SDKAnalyzers",
+ @$"{installDir}\SDKAnalyzers");
+
+ AddFolder(sb,
+ @"WebSDKAnalyzers",
+ @$"{installDir}\WebSDKAnalyzers");
+
+ File.WriteAllText(OutputFile, sb.ToString());
+
+ return true;
+ }
+
+ private void AddFolder(StringBuilder sb, string relativeSourcePath, string swrInstallDir, bool ngenAssemblies = true, IEnumerable filesToInclude = null)
+ {
+ string sourceFolder = Path.Combine(RuntimeAnalyzersLayoutDirectory, relativeSourcePath);
+
+ // If files were specified explicitly, check that they exist.
+ if (filesToInclude != null)
+ {
+ foreach (var file in filesToInclude)
+ {
+ var path = Path.Combine(sourceFolder, file);
+ if (!File.Exists(path))
+ {
+ throw new InvalidOperationException($"File not found: {path}");
+ }
+ }
+ }
+
+ IEnumerable files = filesToInclude ??
+ Directory.GetFiles(sourceFolder)
+ .Where(static f =>
+ {
+ var extension = Path.GetExtension(f);
+ return !extension.Equals(".pdb", StringComparison.OrdinalIgnoreCase) &&
+ !extension.Equals(".swr", StringComparison.OrdinalIgnoreCase) &&
+ !Path.GetFileName(f).Equals("_._");
+ });
+
+ if (files.Any())
+ {
+ sb.Append(@"folder ""InstallDir:\");
+ sb.Append(swrInstallDir);
+ sb.AppendLine(@"\""");
+
+ foreach (var file in files)
+ {
+ var fileName = Path.GetFileName(file);
+
+ sb.Append(@" file source=""$(PkgVS_Redist_Common_Net_Core_SDK_RuntimeAnalyzers)\");
+ sb.Append(Path.Combine(relativeSourcePath, fileName));
+ sb.Append('"');
+
+ if (ngenAssemblies && file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
+ {
+ sb.Append(@" vs.file.ngenApplications=""[installDir]\Common7\IDE\vsn.exe""");
+ }
+
+ sb.AppendLine();
+ }
+
+ sb.AppendLine();
+ }
+
+ // Don't go to sub-folders if the list of files was explicitly specified.
+ if (filesToInclude != null)
+ {
+ return;
+ }
+
+ foreach (var subfolder in Directory.GetDirectories(sourceFolder))
+ {
+ string subfolderName = Path.GetFileName(subfolder);
+ string newRelativeSourcePath = Path.Combine(relativeSourcePath, subfolderName);
+ string newSwrInstallDir = Path.Combine(swrInstallDir, subfolderName);
+
+ // Don't propagate ngenAssemblies to subdirectories.
+ AddFolder(sb, newRelativeSourcePath, newSwrInstallDir);
+ }
+ }
+
+ private static readonly string SWR_HEADER = @"use vs
+
+package name=Microsoft.Net.Core.SDK.RuntimeAnalyzers
+ version=$(ProductsBuildVersion)
+ vs.package.internalRevision=$(PackageInternalRevision)
+
+";
+ }
+}
diff --git a/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets b/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets
index d7a75eb6ce39..6f668d164f91 100644
--- a/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets
+++ b/src/Tasks/sdk-tasks/sdk-tasks.InTree.targets
@@ -19,6 +19,7 @@
+
diff --git a/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj b/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj
new file mode 100644
index 000000000000..bae778ced676
--- /dev/null
+++ b/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net472
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/SdkAnalyzerAssemblyRedirectorTests.cs b/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/SdkAnalyzerAssemblyRedirectorTests.cs
new file mode 100644
index 000000000000..62a042d8f419
--- /dev/null
+++ b/test/Microsoft.Net.Sdk.AnalyzerRedirecting.Tests/SdkAnalyzerAssemblyRedirectorTests.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Net.Sdk.AnalyzerRedirecting.Tests;
+
+public class SdkAnalyzerAssemblyRedirectorTests(ITestOutputHelper log) : SdkTest(log)
+{
+ [Theory]
+ [InlineData("9.0.0-preview.5.24306.11", "9.0.0-preview.7.24406.2")]
+ [InlineData("9.0.0-preview.5.24306.11", "9.0.1-preview.7.24406.2")]
+ [InlineData("9.0.100", "9.0.0-preview.7.24406.2")]
+ [InlineData("9.0.100", "9.0.200")]
+ [InlineData("9.0.100", "9.0.101")]
+ public void SameMajorMinorVersion(string a, string b)
+ {
+ TestDirectory testDir = _testAssetsManager.CreateTestDirectory(identifier: "RuntimeAnalyzers");
+
+ var vsDir = Path.Combine(testDir.Path, "vs");
+ var vsAnalyzerPath = FakeDll(vsDir, @$"AspNetCoreAnalyzers\{a}\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers");
+ var sdkAnalyzerPath = FakeDll(testDir.Path, @$"sdk\packs\Microsoft.AspNetCore.App.Ref\{b}\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers");
+
+ var resolver = new SdkAnalyzerAssemblyRedirector(vsDir);
+ var redirected = resolver.RedirectPath(sdkAnalyzerPath);
+ redirected.Should().Be(vsAnalyzerPath);
+ }
+
+ [Fact]
+ public void DifferentPathSuffix()
+ {
+ TestDirectory testDir = _testAssetsManager.CreateTestDirectory(identifier: "RuntimeAnalyzers");
+
+ var vsDir = Path.Combine(testDir.Path, "vs");
+ FakeDll(vsDir, @"AspNetCoreAnalyzers\9.0.0-preview.5.24306.11\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers");
+ var sdkAnalyzerPath = FakeDll(testDir.Path, @"sdk\packs\Microsoft.AspNetCore.App.Ref\9.0.0-preview.7.24406.2\analyzers\dotnet\vb", "Microsoft.AspNetCore.App.Analyzers");
+
+ var resolver = new SdkAnalyzerAssemblyRedirector(vsDir);
+ var redirected = resolver.RedirectPath(sdkAnalyzerPath);
+ redirected.Should().BeNull();
+ }
+
+ [Theory]
+ [InlineData("8.0.100", "9.0.0-preview.7.24406.2")]
+ [InlineData("9.1.100", "9.0.0-preview.7.24406.2")]
+ [InlineData("9.1.0-preview.5.24306.11", "9.0.0-preview.7.24406.2")]
+ [InlineData("9.0.100", "9.1.100")]
+ [InlineData("9.0.100", "10.0.100")]
+ [InlineData("9.9.100", "9.10.100")]
+ public void DifferentMajorMinorVersion(string a, string b)
+ {
+ TestDirectory testDir = _testAssetsManager.CreateTestDirectory(identifier: "RuntimeAnalyzers");
+
+ var vsDir = Path.Combine(testDir.Path, "vs");
+ FakeDll(vsDir, @$"AspNetCoreAnalyzers\{a}\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers");
+ var sdkAnalyzerPath = FakeDll(testDir.Path, @$"sdk\packs\Microsoft.AspNetCore.App.Ref\{b}\analyzers\dotnet\cs", "Microsoft.AspNetCore.App.Analyzers");
+
+ var resolver = new SdkAnalyzerAssemblyRedirector(vsDir);
+ var redirected = resolver.RedirectPath(sdkAnalyzerPath);
+ redirected.Should().BeNull();
+ }
+
+ private static string FakeDll(string root, string subdir, string name)
+ {
+ var dllPath = Path.Combine(root, subdir, $"{name}.dll");
+ Directory.CreateDirectory(Path.GetDirectoryName(dllPath));
+ File.WriteAllText(dllPath, "");
+ return dllPath;
+ }
+}
diff --git a/test/UnitTests.proj b/test/UnitTests.proj
index 884a36255e87..48ce949add9d 100644
--- a/test/UnitTests.proj
+++ b/test/UnitTests.proj
@@ -12,7 +12,11 @@
-
+
+
+ net472
+ net472
+