Add source-build smoke test to compare assembly versions (#17073)

This commit is contained in:
Matt Thalman 2023-07-28 09:48:27 -05:00 committed by GitHub
parent 1f757f260c
commit 42178804be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 278 additions and 37 deletions

View file

@ -106,7 +106,7 @@ jobs:
- script: >
.dotnet/dotnet test
$(Build.SourcesDirectory)/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/Microsoft.DotNet.SourceBuild.SmokeTests.csproj
--filter "FullyQualifiedName=Microsoft.DotNet.SourceBuild.SmokeTests.SdkContentTests.CompareMsftToSb"
--filter "Category=SdkContent"
--logger:'trx;LogFileName=$(Agent.JobName)_SDKDiffTests.trx'
--logger:'console;verbosity=detailed'
-c Release

View file

@ -8,6 +8,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileSystemGlobbing;
using Xunit;
using Xunit.Abstractions;
@ -15,6 +16,11 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
{
internal class BaselineHelper
{
private const string VersionPlaceholder = "x.y.z";
private const string VersionPlaceholderMatchingPattern = "*.*.*"; // wildcard pattern used to match on the version represented by the placeholder
private const string NetTfmPlaceholder = "netx.y";
private const string NetTfmPlaceholderMatchingPattern = "net*.*"; // wildcard pattern used to match on the version represented by the placeholder
public static void CompareEntries(string baselineFileName, IOrderedEnumerable<string> actualEntries)
{
IEnumerable<string> baseline = File.ReadAllLines(GetBaselineFilePath(baselineFileName));
@ -35,18 +41,17 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
Assert.Null(message);
}
public static void CompareContents(string baselineFileName, string actualContents, ITestOutputHelper outputHelper, bool warnOnDiffs = false)
public static void CompareBaselineContents(string baselineFileName, string actualContents, ITestOutputHelper outputHelper, bool warnOnDiffs = false)
{
string actualFilePath = Path.Combine(DotNetHelper.LogsDirectory, $"Updated{baselineFileName}");
File.WriteAllText(actualFilePath, actualContents);
CompareFiles(baselineFileName, actualFilePath, outputHelper, warnOnDiffs);
CompareFiles(GetBaselineFilePath(baselineFileName), actualFilePath, outputHelper, warnOnDiffs);
}
public static void CompareFiles(string baselineFileName, string actualFilePath, ITestOutputHelper outputHelper, bool warnOnDiffs = false)
public static void CompareFiles(string expectedFilePath, string actualFilePath, ITestOutputHelper outputHelper, bool warnOnDiffs = false)
{
string baselineFilePath = GetBaselineFilePath(baselineFileName);
string baselineFileText = File.ReadAllText(baselineFilePath).Trim();
string baselineFileText = File.ReadAllText(expectedFilePath).Trim();
string actualFileText = File.ReadAllText(actualFilePath).Trim();
string? message = null;
@ -54,9 +59,9 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
if (baselineFileText != actualFileText)
{
// Retrieve a diff in order to provide a UX which calls out the diffs.
string diff = DiffFiles(baselineFilePath, actualFilePath, outputHelper);
string diff = DiffFiles(expectedFilePath, actualFilePath, outputHelper);
string prefix = warnOnDiffs ? "##vso[task.logissue type=warning;]" : string.Empty;
message = $"{Environment.NewLine}{prefix}Baseline '{baselineFilePath}' does not match actual '{actualFilePath}`. {Environment.NewLine}"
message = $"{Environment.NewLine}{prefix}Expected file '{expectedFilePath}' does not match actual file '{actualFilePath}`. {Environment.NewLine}"
+ $"{diff}{Environment.NewLine}";
if (warnOnDiffs)
@ -83,13 +88,13 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
public static string GetAssetsDirectory() => Path.Combine(Directory.GetCurrentDirectory(), "assets");
private static string GetBaselineFilePath(string baselineFileName) => Path.Combine(GetAssetsDirectory(), "baselines", baselineFileName);
public static string GetBaselineFilePath(string baselineFileName) => Path.Combine(GetAssetsDirectory(), "baselines", baselineFileName);
public static string RemoveNetTfmPaths(string source)
{
string pathSeparator = Regex.Escape(Path.DirectorySeparatorChar.ToString());
Regex netTfmRegex = new($"{pathSeparator}net[1-9]+\\.[0-9]+{pathSeparator}");
return netTfmRegex.Replace(source, $"{Path.DirectorySeparatorChar}netx.y{Path.DirectorySeparatorChar}");
return netTfmRegex.Replace(source, $"{Path.DirectorySeparatorChar}{NetTfmPlaceholder}{Path.DirectorySeparatorChar}");
}
public static string RemoveRids(string diff, bool isPortable = false) =>
@ -103,9 +108,23 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
$"(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-]+)*))?");
string result = semanticVersionRegex.Replace(source, $"x.y.z");
string result = semanticVersionRegex.Replace(source, VersionPlaceholder);
return RemoveNetTfmPaths(result);
}
/// <summary>
/// This returns a <see cref="Matcher"/> that can be used to match on a path whose versions have been removed via
/// <see cref="RemoveVersions(string)"/>.
/// </summary>
public static Matcher GetFileMatcherFromPath(string path)
{
path = path
.Replace(VersionPlaceholder, VersionPlaceholderMatchingPattern)
.Replace(NetTfmPlaceholder, NetTfmPlaceholderMatchingPattern);
Matcher matcher = new();
matcher.AddInclude(path);
return matcher;
}
}
}

View file

@ -33,6 +33,6 @@ public class DotNetFormatTests : SmokeTests
DotNetHelper.ExecuteCmd($"format {projectFilePath}");
BaselineHelper.CompareFiles(ExpectedFormattedFileName, testFilePath, OutputHelper);
BaselineHelper.CompareFiles(BaselineHelper.GetBaselineFilePath(ExpectedFormattedFileName), testFilePath, OutputHelper);
}
}

View file

@ -27,7 +27,7 @@ namespace Microsoft.DotNet.SourceBuild.SmokeTests
currentPoisonReport = BaselineHelper.RemoveRids(currentPoisonReport, true);
currentPoisonReport = BaselineHelper.RemoveVersions(currentPoisonReport);
BaselineHelper.CompareContents("PoisonUsage.txt", currentPoisonReport, OutputHelper);
BaselineHelper.CompareBaselineContents("PoisonUsage.txt", currentPoisonReport, OutputHelper);
}
private static string RemoveHashes(string source) => Regex.Replace(source, "^\\s*<Hash>.*</Hash>(\r\n?|\n)", string.Empty, RegexOptions.Multiline);

View file

@ -4,16 +4,24 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.IO.Enumeration;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Extensions.FileSystemGlobbing;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.DotNet.SourceBuild.SmokeTests;
[Trait("Category", "SdkContent")]
public class SdkContentTests : SmokeTests
{
private const string MsftSdkType = "msft";
private const string SourceBuildSdkType = "sb";
public SdkContentTests(ITestOutputHelper outputHelper) : base(outputHelper) { }
/// <Summary>
@ -24,16 +32,163 @@ public class SdkContentTests : SmokeTests
/// in the baseline may appear identical if the diff is version specific.
/// </Summary>
[SkippableFact(new[] { Config.MsftSdkTarballPathEnv, Config.SdkTarballPathEnv }, skipOnNullOrWhiteSpace: true)]
public void CompareMsftToSb()
public void CompareMsftToSbFileList()
{
const string msftFileListingFileName = "msftSdkFiles.txt";
const string sbFileListingFileName = "sbSdkFiles.txt";
WriteTarballFileList(Config.MsftSdkTarballPath, msftFileListingFileName, isPortable: true, "msft");
WriteTarballFileList(Config.SdkTarballPath, sbFileListingFileName, isPortable: false, "sb");
WriteTarballFileList(Config.MsftSdkTarballPath, msftFileListingFileName, isPortable: true, MsftSdkType);
WriteTarballFileList(Config.SdkTarballPath, sbFileListingFileName, isPortable: false, SourceBuildSdkType);
string diff = BaselineHelper.DiffFiles(msftFileListingFileName, sbFileListingFileName, OutputHelper);
diff = RemoveDiffMarkers(diff);
BaselineHelper.CompareContents("MsftToSbSdk.diff", diff, OutputHelper, Config.WarnOnSdkContentDiffs);
BaselineHelper.CompareBaselineContents("MsftToSbSdkFiles.diff", diff, OutputHelper, Config.WarnOnSdkContentDiffs);
}
[SkippableFact(new[] { Config.MsftSdkTarballPathEnv, Config.SdkTarballPathEnv }, skipOnNullOrWhiteSpace: true)]
public void CompareMsftToSbAssemblyVersions()
{
Assert.NotNull(Config.MsftSdkTarballPath);
Assert.NotNull(Config.SdkTarballPath);
DirectoryInfo tempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
try
{
DirectoryInfo sbSdkDir = Directory.CreateDirectory(Path.Combine(tempDir.FullName, SourceBuildSdkType));
Utilities.ExtractTarball(Config.SdkTarballPath, sbSdkDir.FullName, OutputHelper);
DirectoryInfo msftSdkDir = Directory.CreateDirectory(Path.Combine(tempDir.FullName, MsftSdkType));
Utilities.ExtractTarball(Config.MsftSdkTarballPath, msftSdkDir.FullName, OutputHelper);
Dictionary<string, Version?> sbSdkAssemblyVersions = GetSbSdkAssemblyVersions(sbSdkDir.FullName);
Dictionary<string, Version?> msftSdkAssemblyVersions = GetMsftSdkAssemblyVersions(msftSdkDir.FullName, sbSdkAssemblyVersions);
RemoveExcludedAssemblyVersionPaths(sbSdkAssemblyVersions, msftSdkAssemblyVersions);
const string SbVersionsFileName = "sb_assemblyversions.txt";
WriteAssemblyVersionsToFile(sbSdkAssemblyVersions, SbVersionsFileName);
const string MsftVersionsFileName = "msft_assemblyversions.txt";
WriteAssemblyVersionsToFile(msftSdkAssemblyVersions, MsftVersionsFileName);
string diff = BaselineHelper.DiffFiles(MsftVersionsFileName, SbVersionsFileName, OutputHelper);
diff = RemoveDiffMarkers(diff);
BaselineHelper.CompareBaselineContents("MsftToSbSdkAssemblyVersions.diff", diff, OutputHelper, Config.WarnOnSdkContentDiffs);
}
finally
{
tempDir.Delete(recursive: true);
}
}
private static void RemoveExcludedAssemblyVersionPaths(Dictionary<string, Version?> sbSdkAssemblyVersions, Dictionary<string, Version?> msftSdkAssemblyVersions)
{
IEnumerable<string> assemblyVersionDiffFilters = GetSdkAssemblyVersionDiffExclusionFilters()
.Select(filter => filter.TrimStart("./".ToCharArray()));
// Remove any excluded files as long as SB SDK's file has the same or greater assembly version compared to the corresponding
// file 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.
string[] sbSdkFileArray = sbSdkAssemblyVersions.Keys.ToArray();
for (int i = sbSdkFileArray.Length - 1; i >= 0; i--)
{
string assemblyPath = sbSdkFileArray[i];
Version? sbVersion = sbSdkAssemblyVersions[assemblyPath];
Version? msftVersion = msftSdkAssemblyVersions[assemblyPath];
if (sbVersion is not null &&
msftVersion is not null &&
sbVersion >= msftVersion &&
IsFileExcluded(assemblyPath, assemblyVersionDiffFilters))
{
sbSdkAssemblyVersions.Remove(assemblyPath);
msftSdkAssemblyVersions.Remove(assemblyPath);
}
}
}
private static void WriteAssemblyVersionsToFile(Dictionary<string, Version?> assemblyVersions, string outputPath)
{
string[] lines = assemblyVersions
.Select(kvp => $"{kvp.Key} - {kvp.Value}")
.Order()
.ToArray();
File.WriteAllLines(outputPath, lines);
}
private Dictionary<string, Version?> GetMsftSdkAssemblyVersions(
string msftSdkPath, Dictionary<string, Version?> sbSdkAssemblyVersions)
{
Dictionary<string, Version?> msftSdkAssemblyVersions = new();
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))
{
continue;
}
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
msftSdkAssemblyVersions.Add(BaselineHelper.RemoveVersions(relativePath), GetVersion(assemblyName));
}
return msftSdkAssemblyVersions;
}
// It's known that assembly versions can be different between builds in their revision field. Disregard that difference
// by excluding that field in the output.
private static Version? GetVersion(AssemblyName assemblyName)
{
if (assemblyName.Version is not null)
{
return new Version(assemblyName.Version.ToString(3));
}
return null;
}
private string FindMatchingFilePath(string rootDir, Matcher matcher, string representativeFile)
{
foreach (string file in Directory.EnumerateFiles(rootDir, "*", SearchOption.AllDirectories))
{
if (matcher.Match(rootDir, file).HasMatches)
{
return file;
}
}
Assert.Fail($"Unable to find matching file for '{representativeFile}' in '{rootDir}'.");
return string.Empty;
}
private Dictionary<string, Version?> GetSbSdkAssemblyVersions(string sbSdkPath)
{
IEnumerable<string> exclusionFilters = GetSdkDiffExclusionFilters(SourceBuildSdkType)
.Select(filter => filter.TrimStart("./".ToCharArray()));
Dictionary<string, Version?> sbSdkAssemblyVersions = new();
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))
{
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
string relativePath = Path.GetRelativePath(sbSdkPath, file);
string normalizedPath = BaselineHelper.RemoveVersions(relativePath);
if (!IsFileExcluded(normalizedPath, exclusionFilters))
{
sbSdkAssemblyVersions.Add(normalizedPath, GetVersion(assemblyName));
}
}
}
return sbSdkAssemblyVersions;
}
private void WriteTarballFileList(string? tarballPath, string outputFileName, bool isPortable, string sdkType)
@ -47,31 +202,38 @@ public class SdkContentTests : SmokeTests
fileListing = BaselineHelper.RemoveRids(fileListing, isPortable);
fileListing = BaselineHelper.RemoveVersions(fileListing);
IEnumerable<string> files = fileListing.Split(Environment.NewLine).OrderBy(path => path);
files = RemoveExclusions(
files,
GetExclusionFilters(
Path.Combine(BaselineHelper.GetAssetsDirectory(), "SdkDiffExclusions.txt"),
sdkType));
files = RemoveExclusions(files, GetSdkDiffExclusionFilters(sdkType));
File.WriteAllLines(outputFileName, files);
}
private static IEnumerable<string> RemoveExclusions(IEnumerable<string> files, IEnumerable<string> exclusions) =>
files.Where(item => !exclusions.Any(p => FileSystemName.MatchesSimpleExpression(p, item)));
private static IEnumerable<string> RemoveExclusions(IEnumerable<string> files, IEnumerable<string> exclusions) =>
files.Where(item => !IsFileExcluded(item, exclusions));
private static IEnumerable<string> GetExclusionFilters(string exclusionsFilePath, string sdkType)
{
int prefixSkip = sdkType.Length + 1;
return File.ReadAllLines(exclusionsFilePath)
.Where(line => line.StartsWith(sdkType + ",")) // process only specific sdk exclusions
.Select(line =>
{
// Ignore comments
var index = line.IndexOf('#');
return index >= 0 ? line[prefixSkip..index].TrimEnd() : line[prefixSkip..];
})
.ToList();
}
private static bool IsFileExcluded(string filePath, IEnumerable<string> exclusions) =>
exclusions.Any(p => FileSystemName.MatchesSimpleExpression(p, filePath));
private static IEnumerable<string> GetSdkDiffExclusionFilters(string sdkType) =>
ParseExclusionsFile("SdkFileDiffExclusions.txt", sdkType);
private static IEnumerable<string> GetSdkAssemblyVersionDiffExclusionFilters() =>
ParseExclusionsFile("SdkAssemblyVersionDiffExclusions.txt");
private static IEnumerable<string> ParseExclusionsFile(string exclusionsFileName, string? prefix = null)
{
string exclusionsFilePath = Path.Combine(BaselineHelper.GetAssetsDirectory(), exclusionsFileName);
int prefixSkip = prefix?.Length + 1 ?? 0;
return File.ReadAllLines(exclusionsFilePath)
// process only specific sdk exclusions if a prefix is provided
.Where(line => prefix is null || line.StartsWith(prefix + ","))
.Select(line =>
{
// Ignore comments
var index = line.IndexOf('#');
return index >= 0 ? line[prefixSkip..index].TrimEnd() : line[prefixSkip..];
})
.ToList();
}
private static string RemoveDiffMarkers(string source)
{

View file

@ -0,0 +1,39 @@
# 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 '*'
# Referenced 6.0/7.0 assemblies (https://github.com/dotnet/sdk/issues/34245)
./sdk/x.y.z/Containers/tasks/netx.y/runtimes/win/lib/netx.y/?*
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools/netx.y/any/System.Composition.*
./sdk/x.y.z/Microsoft.Extensions.FileProviders.Abstractions.dll
./sdk/x.y.z/Microsoft.Extensions.FileSystemGlobbing.dll
./sdk/x.y.z/Sdks/Microsoft.NET.Sdk.Razor/source-generators/System.Collections.Immutable.dll
./sdk/x.y.z/Sdks/Microsoft.NET.Sdk.Razor/**/Microsoft.Extensions.ObjectPool.dll
./sdk/**/System.Configuration.ConfigurationManager.dll
./sdk/**/System.Diagnostics.EventLog*.dll
./sdk/**/System.Reflection.MetadataLoadContext.dll
./sdk/**/System.Security.Cryptography.Pkcs.dll
./sdk/**/System.Security.Cryptography.ProtectedData.dll
./sdk/x.y.z/System.Security.Cryptography.Xml.dll
# These assemblies are lifted to a higher version naturally via SB
./sdk/x.y.z/DotnetTools/dotnet-format/*/Microsoft.CodeAnalysis.*
./sdk/x.y.z/DotnetTools/dotnet-format/Humanizer.dll
./sdk/x.y.z/DotnetTools/dotnet-format/Microsoft.Build.Locator.dll
./sdk/x.y.z/DotnetTools/dotnet-format/Microsoft.CodeAnalysis.*
./sdk/x.y.z/DotnetTools/dotnet-format/Microsoft.DiaSymReader.dll
./sdk/x.y.z/DotnetTools/dotnet-format/System.Composition.*
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools/netx.y/any/Humanizer.dll
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools/netx.y/any/Microsoft.Build.Locator.dll
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools/netx.y/any/Microsoft.CodeAnalysis.AnalyzerUtilities.dll
./sdk/x.y.z/DotnetTools/dotnet-watch/x.y.z/tools/netx.y/any/Microsoft.DiaSymReader.dll
./sdk/x.y.z/Sdks/Microsoft.NET.Sdk.Razor/source-generators/Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll

View file

@ -0,0 +1,21 @@
diff --git a/msft_assemblyversions.txt b/sb_assemblyversions.txt
index ------------
--- a/msft_assemblyversions.txt
+++ b/sb_assemblyversions.txt
@@ ------------ @@ sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.Extensio
sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.Extensions.FileProviders.Physical.dll - 8.0.0
sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.Extensions.FileSystemGlobbing.dll - 8.0.0
sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.Extensions.Primitives.dll - 8.0.0
-sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.Abstractions.dll - 7.0.0
-sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.JsonWebTokens.dll - 7.0.0
-sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.Logging.dll - 7.0.0
-sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.Tokens.dll - 7.0.0
-sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/System.IdentityModel.Tokens.Jwt.dll - 7.0.0
+sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.Abstractions.dll - 0.0.1
+sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.JsonWebTokens.dll - 0.0.1
+sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.Logging.dll - 0.0.1
+sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/Microsoft.IdentityModel.Tokens.dll - 0.0.1
+sdk/x.y.z/DotnetTools/dotnet-user-jwts/x.y.z/tools/netx.y/any/System.IdentityModel.Tokens.Jwt.dll - 0.0.1
sdk/x.y.z/DotnetTools/dotnet-user-secrets/x.y.z/tools/netx.y/any/dotnet-user-secrets.dll - 8.0.0
sdk/x.y.z/DotnetTools/dotnet-user-secrets/x.y.z/tools/netx.y/any/Microsoft.Extensions.Configuration.Abstractions.dll - 8.0.0
sdk/x.y.z/DotnetTools/dotnet-user-secrets/x.y.z/tools/netx.y/any/Microsoft.Extensions.Configuration.dll - 8.0.0