Create test project for Unified Build validation

This commit is contained in:
Jackson Schuster 2024-03-13 13:53:24 -07:00
parent 08364741b0
commit 158c2aa0ed
12 changed files with 303 additions and 46 deletions

View file

@ -215,7 +215,7 @@ public static class VersionIdentifier
/// <returns>Asset name without versions</returns>
public static string RemoveVersions(string assetName, string replacement = "")
{
string[] pathSegments = assetName.Split('/');
string[] pathSegments = assetName.Split('/', '\\');
// Remove the version number from each segment, then join back together and
// remove any useless character sequences.
@ -252,6 +252,6 @@ public static class VersionIdentifier
public static bool AreVersionlessEqual(string assetName1, string assetName2)
{
return RemoveVersions(assetName1) == RemoveVersions(assetName2);
return RemoveVersions(assetName1, "{VERSION}") == RemoveVersions(assetName2, "{VERSION}");
}
}

View file

@ -0,0 +1,31 @@
<Project>
<PropertyGroup>
<UnifiedBuildValidationTestsDir>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'test', 'Microsoft.DotNet.UnifiedBuild.Tests'))</UnifiedBuildValidationTestsDir>
</PropertyGroup>
<Target Name="RunUnifiedBuildValidation">
<ItemGroup>
<SdkTarballItem Include="$(ArtifactsAssetsDir)dotnet-sdk*$(ArchiveExtension)" />
<SourceBuiltArtifactsItem Include="$(ArtifactsAssetsDir)$(SourceBuiltArtifactsTarballName).*$(ArchiveExtension)" />
</ItemGroup>
<PropertyGroup>
<!-- <SdkTarballPath>%(SdkTarballItem.Identity)</SdkTarballPath> -->
<SdkBaselineValidationVerbosity Condition="'$(SdkBaselineValidationVerbosity)' == ''">normal</SdkBaselineValidationVerbosity>
</PropertyGroup>
<!-- Multiple loggers are specified so that results are captured in trx and pipelines can fail with AzDO pipeline warnings -->
<Exec Command="$(DotnetTool) test $(UnifiedBuildValidationTestsDir) --logger:trx -c $(Configuration) -p:VSTestUseMSBuildOutput=false"
IgnoreStandardErrorWarningFormat="true"
EnvironmentVariables="
UNIFIED_BUILD_VALIDATION_SDK_TARBALL_PATH=$(SdkTarballPath);
UNIFIED_BUILD_VALIDATION_SOURCEBUILT_ARTIFACTS_PATH=$(SourceBuiltArtifactsPath);
UNIFIED_BUILD_VALIDATION_TARGET_RID=$(TargetRid);
UNIFIED_BUILD_VALIDATION_PORTABLE_RID=$(PortableRid);
UNIFIED_BUILD_VALIDATION_CUSTOM_PACKAGES_PATH=$(CustomSourceBuiltPackagesPath);
UNIFIED_BUILD_VALIDATION_WARN_SDK_CONTENT_DIFFS=true;
$(CustomTestEnvVars)" />
</Target>
</Project>

View file

@ -96,7 +96,7 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
public static string RemoveVersions(string source)
{
// Remove version numbers for examples like "roslyn4.1", "net8.0", and "netstandard2.1".
string pathSeparator = Regex.Escape(Path.DirectorySeparatorChar.ToString());
string pathSeparator = $"[{Regex.Escape(@"\")}|{Regex.Escape(@"/")}]";
string result = Regex.Replace(source, $@"{pathSeparator}(net|roslyn)[1-9]+\.[0-9]+{pathSeparator}", match =>
{
string wordPart = match.Groups[1].Value;
@ -111,10 +111,10 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
// - The version may have one or more release identifiers that begin with '.' or '-'
// - The version should end before a path separator, '.', '-', or '/'
Regex semanticVersionRegex = new(
@"(?<=[./-])(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))+"
@"(?<=[./\\-])(0|[1-9]\d*)\.(0|[1-9]\d*)(\.(0|[1-9]\d*))+"
+ @"(((?:[-.]((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)))+"
+ @"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
+ @"(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?(?=[/.-])");
+ @"(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?(?=[/\\.-])");
return semanticVersionRegex.Replace(result, SemanticVersionPlaceholder);
}

View file

@ -43,16 +43,16 @@ internal class DotNetHelper
}
IsMonoRuntime = DetermineIsMonoRuntime(Config.DotNetDirectory);
if (!Directory.Exists(ProjectsDirectory))
{
Directory.CreateDirectory(ProjectsDirectory);
InitNugetConfig();
}
// if (!Directory.Exists(ProjectsDirectory))
// {
// Directory.CreateDirectory(ProjectsDirectory);
// InitNugetConfig();
// }
if (!Directory.Exists(PackagesDirectory))
{
Directory.CreateDirectory(PackagesDirectory);
}
// if (!Directory.Exists(PackagesDirectory))
// {
// Directory.CreateDirectory(PackagesDirectory);
// }
}
}
@ -105,7 +105,7 @@ internal class DotNetHelper
OutputHelper,
configureCallback: (process) => configureProcess(process, workingDirectory),
millisecondTimeout: millisecondTimeout);
if (expectedExitCode != null) {
ExecuteHelper.ValidateExitCode(executeResult, (int) expectedExitCode);
}
@ -243,7 +243,7 @@ internal class DotNetHelper
{
throw validator.ValidationException;
}
}
}
private static string GetBinLogOption(string projectName, string command, string? differentiator = null)
{

View file

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.IO;
@ -10,6 +11,7 @@ using System.IO.Enumeration;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.FileSystemGlobbing;
using Xunit;
using Xunit.Abstractions;
@ -118,27 +120,33 @@ public class SdkContentTests : SdkTests
private Dictionary<string, Version?> GetMsftSdkAssemblyVersions(
string msftSdkPath, Dictionary<string, Version?> sbSdkAssemblyVersions)
{
Dictionary<string, Version?> msftSdkAssemblyVersions = new();
ConcurrentDictionary<string, Version?> msftSdkAssemblyVersions = new();
var tasks = new List<Task>();
foreach ((string relativePath, _) in sbSdkAssemblyVersions)
{
// Now we want to find the corresponding file that exists in the MSFT SDK.
// We've already replaced version numbers with placeholders in the path.
// So we can't directly use the relative path to find the corresponding file. Instead,
// we need to replace the version placeholders with wildcards and find the path through path matching.
string file = Path.Combine(msftSdkPath, relativePath);
Matcher matcher = BaselineHelper.GetFileMatcherFromPath(relativePath);
file = FindMatchingFilePath(msftSdkPath, matcher, relativePath);
if (!File.Exists(file))
var t = Task.Run(() =>
{
continue;
}
// Now we want to find the corresponding file that exists in the MSFT SDK.
// We've already replaced version numbers with placeholders in the path.
// So we can't directly use the relative path to find the corresponding file. Instead,
// we need to replace the version placeholders with wildcards and find the path through path matching.
string file = Path.Combine(msftSdkPath, relativePath);
Matcher matcher = BaselineHelper.GetFileMatcherFromPath(relativePath);
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
msftSdkAssemblyVersions.Add(BaselineHelper.RemoveVersions(relativePath), GetVersion(assemblyName));
file = FindMatchingFilePath(msftSdkPath, matcher, relativePath);
if (!File.Exists(file))
{
return;
}
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
Assert.True(msftSdkAssemblyVersions.TryAdd(BaselineHelper.RemoveVersions(relativePath), GetVersion(assemblyName)));
});
tasks.Add(t);
}
return msftSdkAssemblyVersions;
Task.WaitAll(tasks.ToArray());
return msftSdkAssemblyVersions.ToDictionary();
}
// It's known that assembly versions can be different between builds in their revision field. Disregard that difference
@ -171,24 +179,65 @@ public class SdkContentTests : SdkTests
{
IEnumerable<string> exclusionFilters = GetSdkDiffExclusionFilters(SourceBuildSdkType)
.Select(filter => filter.TrimStart("./".ToCharArray()));
Dictionary<string, Version?> sbSdkAssemblyVersions = new();
foreach (string file in Directory.EnumerateFiles(sbSdkPath, "*", SearchOption.AllDirectories))
ConcurrentDictionary<string, Version?> sbSdkAssemblyVersions = new();
List<Task> tasks = new List<Task>();
foreach (string dir in Directory.EnumerateDirectories(sbSdkPath, "*", SearchOption.AllDirectories).Append(sbSdkPath))
{
string fileExt = Path.GetExtension(file);
if (fileExt.Equals(".dll", StringComparison.OrdinalIgnoreCase) ||
fileExt.Equals(".exe", StringComparison.OrdinalIgnoreCase))
var t = Task.Run(() =>
{
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
string relativePath = Path.GetRelativePath(sbSdkPath, file);
string normalizedPath = BaselineHelper.RemoveVersions(relativePath);
if (!Utilities.IsFileExcluded(normalizedPath, exclusionFilters))
foreach (string file in Directory.EnumerateFiles(dir, "*", SearchOption.TopDirectoryOnly))
{
sbSdkAssemblyVersions.Add(normalizedPath, GetVersion(assemblyName));
string fileExt = Path.GetExtension(file);
if (fileExt.Equals(".dll", StringComparison.OrdinalIgnoreCase) ||
fileExt.Equals(".exe", StringComparison.OrdinalIgnoreCase))
{
string relativePath = Path.GetRelativePath(sbSdkPath, file);
string normalizedPath = BaselineHelper.RemoveVersions(relativePath);
if (!Utilities.IsFileExcluded(normalizedPath, exclusionFilters))
{
try
{
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
Assert.True(sbSdkAssemblyVersions.TryAdd(normalizedPath, GetVersion(assemblyName)));
}
catch (BadImageFormatException)
{
Console.WriteLine($"BadImageFormatException: {file}");
}
}
}
}
}
});
tasks.Add(t);
}
return sbSdkAssemblyVersions;
//foreach (string file in Directory.EnumerateFiles(sbSdkPath, "*", SearchOption.AllDirectories))
//{
// string fileExt = Path.GetExtension(file);
// if (fileExt.Equals(".dll", StringComparison.OrdinalIgnoreCase) ||
// fileExt.Equals(".exe", StringComparison.OrdinalIgnoreCase))
// {
// string relativePath = Path.GetRelativePath(sbSdkPath, file);
// string normalizedPath = BaselineHelper.RemoveVersions(relativePath);
// if (!Utilities.IsFileExcluded(normalizedPath, exclusionFilters))
// {
// var t = Task.Run(() =>
// {
// try
// {
// AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
// sbSdkAssemblyVersions.Add(normalizedPath, GetVersion(assemblyName));
// }
// catch (BadImageFormatException)
// {
// Console.WriteLine($"BadImageFormatException: {file}");
// }
// });
// tasks.Add(t);
// }
// }
//}
Task.WaitAll(tasks.ToArray());
return sbSdkAssemblyVersions.ToDictionary();
}
private void WriteTarballFileList(string? tarballPath, string outputFileName, bool isPortable, string sdkType)

View file

@ -23,7 +23,7 @@ public static class Utilities
/// Returns whether the given file path is excluded by the given exclusions using glob file matching.
/// </summary>
public static bool IsFileExcluded(string filePath, IEnumerable<string> exclusions) =>
GetMatchingFileExclusions(filePath, exclusions, exclusion => exclusion).Any();
GetMatchingFileExclusions(filePath.Replace('\\', '/'), exclusions, exclusion => exclusion).Any();
public static IEnumerable<T> GetMatchingFileExclusions<T>(string filePath, IEnumerable<T> exclusions, Func<T, string> getExclusionExpression) =>
exclusions.Where(exclusion => FileSystemName.MatchesSimpleExpression(getExclusionExpression(exclusion), filePath));

View file

@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
namespace Microsoft.DotNet.SourceBuild.SmokeTests;
internal static class Config
{
public const string DotNetDirectoryEnv = "UNIFIED_BUILD_VALIDATION_DOTNET_DIR";
public const string IncludeArtifactsSizeEnv = "UNIFIED_BUILD_VALIDATION_INCLUDE_ARTIFACTSSIZE";
public const string MsftSdkTarballPathEnv = "UNIFIED_BUILD_VALIDATION_MSFT_SDK_TARBALL_PATH";
public const string PoisonReportPathEnv = "UNIFIED_BUILD_VALIDATION_POISON_REPORT_PATH";
public const string PortableRidEnv = "UNIFIED_BUILD_VALIDATION_PORTABLE_RID";
public const string PrereqsPathEnv = "UNIFIED_BUILD_VALIDATION_PREREQS_PATH";
public const string CustomPackagesPathEnv = "UNIFIED_BUILD_VALIDATION_CUSTOM_PACKAGES_PATH";
public const string SdkTarballPathEnv = "UNIFIED_BUILD_VALIDATION_SDK_TARBALL_PATH";
public const string SourceBuiltArtifactsPathEnv = "UNIFIED_BUILD_VALIDATION_SOURCEBUILT_ARTIFACTS_PATH";
public const string TargetRidEnv = "UNIFIED_BUILD_VALIDATION_TARGET_RID";
public const string WarnSdkContentDiffsEnv = "UNIFIED_BUILD_VALIDATION_WARN_SDK_CONTENT_DIFFS";
public const string WarnLicenseScanDiffsEnv = "UNIFIED_BUILD_VALIDATION_WARN_LICENSE_SCAN_DIFFS";
public const string RunningInCIEnv = "UNIFIED_BUILD_VALIDATION_RUNNING_IN_CI";
public const string LicenseScanPathEnv = "UNIFIED_BUILD_VALIDATION_LICENSE_SCAN_PATH";
public static string DotNetDirectory { get; } =
Environment.GetEnvironmentVariable(DotNetDirectoryEnv) ?? Path.Combine(Directory.GetCurrentDirectory(), ".dotnet");
public static string? MsftSdkTarballPath { get; } = Environment.GetEnvironmentVariable(MsftSdkTarballPathEnv);
public static string PortableRid { get; } = Environment.GetEnvironmentVariable(PortableRidEnv) ??
throw new InvalidOperationException($"'{Config.PortableRidEnv}' must be specified");
public static string? PrereqsPath { get; } = Environment.GetEnvironmentVariable(PrereqsPathEnv);
public static string? CustomPackagesPath { get; } = Environment.GetEnvironmentVariable(CustomPackagesPathEnv);
public static string? SdkTarballPath { get; } = Environment.GetEnvironmentVariable(SdkTarballPathEnv);
public static string? SourceBuiltArtifactsPath { get; } = Environment.GetEnvironmentVariable(SourceBuiltArtifactsPathEnv);
public static string TargetRid { get; } = Environment.GetEnvironmentVariable(TargetRidEnv) ??
throw new InvalidOperationException($"'{Config.TargetRidEnv}' must be specified");
public static string TargetArchitecture { get; } = TargetRid.Split('-')[1];
public static bool WarnOnSdkContentDiffs { get; } =
bool.TryParse(Environment.GetEnvironmentVariable(WarnSdkContentDiffsEnv), out bool warnOnSdkContentDiffs) && warnOnSdkContentDiffs;
public static bool WarnOnLicenseScanDiffs { get; } =
bool.TryParse(Environment.GetEnvironmentVariable(WarnLicenseScanDiffsEnv), out bool warnOnLicenseScanDiffs) && warnOnLicenseScanDiffs;
// Indicates whether the tests are being run in the context of a CI pipeline
public static bool RunningInCI { get; } =
bool.TryParse(Environment.GetEnvironmentVariable(RunningInCIEnv), out bool runningInCI) && runningInCI;
public static string? LicenseScanPath { get; } = Environment.GetEnvironmentVariable(LicenseScanPathEnv);
}

View file

@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<DefaultExcludesInProjectFolder>$(DefaultExcludesInProjectFolder);assets/**/*</DefaultExcludesInProjectFolder>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.5.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\TestBase.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\SdkTests.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\SdkContentTests.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\SkippableFactAttribute.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\SkippableTheoryAttribute.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\DotnetHelper.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\DotnetTemplate.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\Utilities.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\BaselineHelper.cs" />
<Compile Include="..\Microsoft.DotNet.SourceBuild.SmokeTests\ExecuteHelper.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="assets\**"
CopyToOutputDirectory="Always" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\packages\smoke-test-prereqs\*"
CopyToOutputDirectory="Always"
Link="assets\smoke-tests\prereq-packages\%(Filename)%(Extension)" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,16 @@
# Contains the list of files whose assembly versions are to be excluded from comparison between the MSFT & SB SDK.
# These exclusions only take effect if the assembly version of the file in the SB SDK is equal to or greater than
# the version in the MSFT SDK. If the version is less, the file will show up in the results as this is not a scenario
# that is valid for shipping.
#
# This list is processed using FileSystemName.MatchesSimpleExpression
#
# Examples
# 'folder/*' matches 'folder/' and 'folder/abc'
# 'folder/?*' matches 'folder/abc' but not 'folder/'
#
# We do not want to filter-out folder entries, therefore, we should use: '?*' and not just '*'
./sdk/x.y.z/TestHostNetFramework/x64/msdia140.dll
./sdk/x.y.z/TestHostNetFramework/x86/msdia140.dll
./sdk/x.y.z/datacollector.dll

View file

@ -0,0 +1,24 @@
# This list is processed using FileSystemName.MatchesSimpleExpression
#
# Format
# {msft|sb},<path> [# comment]
# msft = Microsoft built SDK
# sb = source-built SDK
#
# Examples
# 'folder/*' matches 'folder/' and 'folder/abc'
# 'folder/?*' matches 'folder/abc' but not 'folder/'
msft,./sdk/x.y.z/TestHostNetFramework/x64/msdia140.dll
msft,./sdk/x.y.z/TestHostNetFramework/x86/msdia140.dll
msft,./sdk/x.y.z/datacollector.dll
sb,./sdk/x.y.z/TestHostNetFramework/x64/msdia140.dll
sb,./sdk/x.y.z/TestHostNetFramework/x86/msdia140.dll
sb,./sdk/x.y.z/datacollector.dll
msft,./sdk/x.y.z/Containers/containerize/*
sb,./sdk/x.y.z/Containers/containerize/*
msft,./sdk/x.y.z/Containers/tasks/net472/*
sb,./sdk/x.y.z/Containers/tasks/net472/*

View file

@ -0,0 +1,43 @@
diff --git a/msftSdkFiles.txt b/sbSdkFiles.txt
index ------------
--- a/msftSdkFiles.txt
+++ b/sbSdkFiles.txt
@@ ------------ @@
./packs/Microsoft.AspNetCore.App.Ref/x.y.z/ref\netx.y\System.Security.Cryptography.Xml.xml
./packs/Microsoft.AspNetCore.App.Ref/x.y.z/ref\netx.y\System.Threading.RateLimiting.dll
./packs/Microsoft.AspNetCore.App.Ref/x.y.z/ref\netx.y\System.Threading.RateLimiting.xml
-./packs/Microsoft.NETCore.App.Host.linux-x64/
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/apphost
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/coreclr_delegates.h
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/hostfxr.h
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/libnethost.a
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/libnethost.so
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/nethost.h
-./packs/Microsoft.NETCore.App.Host.linux-x64/x.y.z/runtimes/linux-x64/native/singlefilehost
+./packs/Microsoft.NETCore.App.Host.banana-rid/
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/apphost
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/coreclr_delegates.h
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/hostfxr.h
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/libnethost.a
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/libnethost.so
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/nethost.h
+./packs/Microsoft.NETCore.App.Host.banana-rid/x.y.z/runtimes/banana-rid/native/singlefilehost
./packs/Microsoft.NETCore.App.Ref/
./packs/Microsoft.NETCore.App.Ref/x.y.z/
./packs/Microsoft.NETCore.App.Ref/x.y.z/analyzers/
@@ ------------ @@
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/runtimes/win/lib/
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/runtimes/win/lib\netx.y\
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/runtimes/win/lib\netx.y\System.Diagnostics.EventLog.dll
-./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/runtimes/win/lib\netx.y\System.Diagnostics.EventLog.Messages.dll
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/runtimes/win/lib\netx.y\System.Security.Cryptography.Pkcs.dll
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/runtimes/win/lib\netx.y\System.Windows.Extensions.dll
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools\netx.y\any/System.CodeDom.dll