Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,518 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal;
internal sealed class DotNetDepsFile
{
private DotNetDepsFile(string relativePath, IReadOnlyDictionary<string, DotNetLibrary> libraries)
{
RelativePath = relativePath;
Libraries = libraries;
}
public string RelativePath { get; }
public IReadOnlyDictionary<string, DotNetLibrary> Libraries { get; }
public static DotNetDepsFile? Load(string absolutePath, string relativePath, CancellationToken cancellationToken)
{
using var stream = File.OpenRead(absolutePath);
using var document = JsonDocument.Parse(stream, new JsonDocumentOptions
{
AllowTrailingCommas = true,
CommentHandling = JsonCommentHandling.Skip
});
var root = document.RootElement;
if (root.ValueKind is not JsonValueKind.Object)
{
return null;
}
var libraries = ParseLibraries(root, cancellationToken);
if (libraries.Count == 0)
{
return null;
}
PopulateTargets(root, libraries, cancellationToken);
return new DotNetDepsFile(relativePath, libraries);
}
private static Dictionary<string, DotNetLibrary> ParseLibraries(JsonElement root, CancellationToken cancellationToken)
{
var result = new Dictionary<string, DotNetLibrary>(StringComparer.Ordinal);
if (!root.TryGetProperty("libraries", out var librariesElement) || librariesElement.ValueKind is not JsonValueKind.Object)
{
return result;
}
foreach (var property in librariesElement.EnumerateObject())
{
cancellationToken.ThrowIfCancellationRequested();
if (DotNetLibrary.TryCreate(property.Name, property.Value, out var library))
{
result[property.Name] = library;
}
}
return result;
}
private static void PopulateTargets(JsonElement root, IDictionary<string, DotNetLibrary> libraries, CancellationToken cancellationToken)
{
if (!root.TryGetProperty("targets", out var targetsElement) || targetsElement.ValueKind is not JsonValueKind.Object)
{
return;
}
foreach (var targetProperty in targetsElement.EnumerateObject())
{
cancellationToken.ThrowIfCancellationRequested();
var (tfm, rid) = ParseTargetKey(targetProperty.Name);
if (targetProperty.Value.ValueKind is not JsonValueKind.Object)
{
continue;
}
foreach (var libraryProperty in targetProperty.Value.EnumerateObject())
{
cancellationToken.ThrowIfCancellationRequested();
if (!libraries.TryGetValue(libraryProperty.Name, out var library))
{
continue;
}
if (!string.IsNullOrEmpty(tfm))
{
library.AddTargetFramework(tfm);
}
if (!string.IsNullOrEmpty(rid))
{
library.AddRuntimeIdentifier(rid);
}
library.MergeTargetMetadata(libraryProperty.Value, tfm, rid);
}
}
}
private static (string tfm, string? rid) ParseTargetKey(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return (string.Empty, null);
}
var separatorIndex = value.IndexOf('/');
if (separatorIndex < 0)
{
return (value.Trim(), null);
}
var tfm = value[..separatorIndex].Trim();
var rid = value[(separatorIndex + 1)..].Trim();
return (tfm, string.IsNullOrEmpty(rid) ? null : rid);
}
}
internal sealed class DotNetLibrary
{
private readonly HashSet<string> _dependencies = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _runtimeIdentifiers = new(StringComparer.Ordinal);
private readonly List<DotNetLibraryAsset> _runtimeAssets = new();
private readonly HashSet<string> _targetFrameworks = new(StringComparer.Ordinal);
private DotNetLibrary(
string key,
string id,
string version,
string type,
bool? serviceable,
string? sha512,
string? path,
string? hashPath)
{
Key = key;
Id = id;
Version = version;
Type = type;
Serviceable = serviceable;
Sha512 = NormalizeValue(sha512);
PackagePath = NormalizePath(path);
HashPath = NormalizePath(hashPath);
}
public string Key { get; }
public string Id { get; }
public string Version { get; }
public string Type { get; }
public bool? Serviceable { get; }
public string? Sha512 { get; }
public string? PackagePath { get; }
public string? HashPath { get; }
public bool IsPackage => string.Equals(Type, "package", StringComparison.OrdinalIgnoreCase);
public IReadOnlyCollection<string> Dependencies => _dependencies;
public IReadOnlyCollection<string> TargetFrameworks => _targetFrameworks;
public IReadOnlyCollection<string> RuntimeIdentifiers => _runtimeIdentifiers;
public IReadOnlyCollection<DotNetLibraryAsset> RuntimeAssets => _runtimeAssets;
public static bool TryCreate(string key, JsonElement element, [NotNullWhen(true)] out DotNetLibrary? library)
{
library = null;
if (!TrySplitNameAndVersion(key, out var id, out var version))
{
return false;
}
var type = element.TryGetProperty("type", out var typeElement) && typeElement.ValueKind == JsonValueKind.String
? typeElement.GetString() ?? string.Empty
: string.Empty;
bool? serviceable = null;
if (element.TryGetProperty("serviceable", out var serviceableElement))
{
if (serviceableElement.ValueKind is JsonValueKind.True)
{
serviceable = true;
}
else if (serviceableElement.ValueKind is JsonValueKind.False)
{
serviceable = false;
}
}
var sha512 = element.TryGetProperty("sha512", out var sha512Element) && sha512Element.ValueKind == JsonValueKind.String
? sha512Element.GetString()
: null;
var path = element.TryGetProperty("path", out var pathElement) && pathElement.ValueKind == JsonValueKind.String
? pathElement.GetString()
: null;
var hashPath = element.TryGetProperty("hashPath", out var hashElement) && hashElement.ValueKind == JsonValueKind.String
? hashElement.GetString()
: null;
library = new DotNetLibrary(key, id, version, type, serviceable, sha512, path, hashPath);
library.MergeLibraryMetadata(element);
return true;
}
public void AddTargetFramework(string tfm)
{
if (!string.IsNullOrWhiteSpace(tfm))
{
_targetFrameworks.Add(tfm);
}
}
public void AddRuntimeIdentifier(string rid)
{
if (!string.IsNullOrWhiteSpace(rid))
{
_runtimeIdentifiers.Add(rid);
}
}
public void MergeTargetMetadata(JsonElement element, string? tfm, string? rid)
{
if (element.TryGetProperty("dependencies", out var dependenciesElement) && dependenciesElement.ValueKind is JsonValueKind.Object)
{
foreach (var dependencyProperty in dependenciesElement.EnumerateObject())
{
AddDependency(dependencyProperty.Name);
}
}
MergeRuntimeAssets(element, tfm, rid);
}
public void MergeLibraryMetadata(JsonElement element)
{
if (element.TryGetProperty("dependencies", out var dependenciesElement) && dependenciesElement.ValueKind is JsonValueKind.Object)
{
foreach (var dependencyProperty in dependenciesElement.EnumerateObject())
{
AddDependency(dependencyProperty.Name);
}
}
MergeRuntimeAssets(element, tfm: null, rid: null);
}
private void MergeRuntimeAssets(JsonElement element, string? tfm, string? rid)
{
AddRuntimeAssetsFromRuntime(element, tfm, rid);
AddRuntimeAssetsFromRuntimeTargets(element, tfm, rid);
}
private void AddRuntimeAssetsFromRuntime(JsonElement element, string? tfm, string? rid)
{
if (!element.TryGetProperty("runtime", out var runtimeElement) || runtimeElement.ValueKind is not JsonValueKind.Object)
{
return;
}
foreach (var assetProperty in runtimeElement.EnumerateObject())
{
if (DotNetLibraryAsset.TryCreateFromRuntime(assetProperty.Name, assetProperty.Value, tfm, rid, out var asset))
{
_runtimeAssets.Add(asset);
}
}
}
private void AddRuntimeAssetsFromRuntimeTargets(JsonElement element, string? tfm, string? rid)
{
if (!element.TryGetProperty("runtimeTargets", out var runtimeTargetsElement) || runtimeTargetsElement.ValueKind is not JsonValueKind.Object)
{
return;
}
foreach (var assetProperty in runtimeTargetsElement.EnumerateObject())
{
if (DotNetLibraryAsset.TryCreateFromRuntimeTarget(assetProperty.Name, assetProperty.Value, tfm, rid, out var asset))
{
_runtimeAssets.Add(asset);
}
}
}
private void AddDependency(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
var dependencyId = name;
if (TrySplitNameAndVersion(name, out var parsedName, out _))
{
dependencyId = parsedName;
}
if (!string.IsNullOrWhiteSpace(dependencyId))
{
_dependencies.Add(dependencyId);
}
}
private static bool TrySplitNameAndVersion(string key, out string name, out string version)
{
name = string.Empty;
version = string.Empty;
if (string.IsNullOrWhiteSpace(key))
{
return false;
}
var separatorIndex = key.LastIndexOf('/');
if (separatorIndex <= 0 || separatorIndex >= key.Length - 1)
{
return false;
}
name = key[..separatorIndex].Trim();
version = key[(separatorIndex + 1)..].Trim();
return name.Length > 0 && version.Length > 0;
}
private static string? NormalizePath(string? path)
{
if (string.IsNullOrWhiteSpace(path))
{
return null;
}
return path.Replace('\\', '/');
}
private static string? NormalizeValue(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return value.Trim();
}
}
internal enum DotNetLibraryAssetKind
{
Runtime,
Native
}
internal sealed record DotNetLibraryAsset(
string RelativePath,
string? TargetFramework,
string? RuntimeIdentifier,
string? AssemblyVersion,
string? FileVersion,
DotNetLibraryAssetKind Kind)
{
public static bool TryCreateFromRuntime(string name, JsonElement element, string? tfm, string? rid, [NotNullWhen(true)] out DotNetLibraryAsset? asset)
{
asset = null;
if (string.IsNullOrWhiteSpace(name))
{
return false;
}
var normalizedPath = NormalizePath(name);
if (string.IsNullOrEmpty(normalizedPath))
{
return false;
}
if (!IsManagedAssembly(normalizedPath))
{
return false;
}
string? assemblyVersion = null;
string? fileVersion = null;
if (element.ValueKind == JsonValueKind.Object)
{
if (element.TryGetProperty("assemblyVersion", out var assemblyVersionElement) && assemblyVersionElement.ValueKind == JsonValueKind.String)
{
assemblyVersion = NormalizeValue(assemblyVersionElement.GetString());
}
if (element.TryGetProperty("fileVersion", out var fileVersionElement) && fileVersionElement.ValueKind == JsonValueKind.String)
{
fileVersion = NormalizeValue(fileVersionElement.GetString());
}
}
asset = new DotNetLibraryAsset(
RelativePath: normalizedPath,
TargetFramework: NormalizeValue(tfm),
RuntimeIdentifier: NormalizeValue(rid),
AssemblyVersion: assemblyVersion,
FileVersion: fileVersion,
Kind: DotNetLibraryAssetKind.Runtime);
return true;
}
public static bool TryCreateFromRuntimeTarget(string name, JsonElement element, string? tfm, string? rid, [NotNullWhen(true)] out DotNetLibraryAsset? asset)
{
asset = null;
if (string.IsNullOrWhiteSpace(name) || element.ValueKind is not JsonValueKind.Object)
{
return false;
}
var assetType = element.TryGetProperty("assetType", out var assetTypeElement) && assetTypeElement.ValueKind == JsonValueKind.String
? NormalizeValue(assetTypeElement.GetString())
: null;
var normalizedPath = NormalizePath(name);
if (string.IsNullOrEmpty(normalizedPath))
{
return false;
}
DotNetLibraryAssetKind kind;
if (assetType is null || string.Equals(assetType, "runtime", StringComparison.OrdinalIgnoreCase))
{
if (!IsManagedAssembly(normalizedPath))
{
return false;
}
kind = DotNetLibraryAssetKind.Runtime;
}
else if (string.Equals(assetType, "native", StringComparison.OrdinalIgnoreCase))
{
kind = DotNetLibraryAssetKind.Native;
}
else
{
return false;
}
string? assemblyVersion = null;
string? fileVersion = null;
if (kind == DotNetLibraryAssetKind.Runtime &&
element.TryGetProperty("assemblyVersion", out var assemblyVersionElement) &&
assemblyVersionElement.ValueKind == JsonValueKind.String)
{
assemblyVersion = NormalizeValue(assemblyVersionElement.GetString());
}
if (kind == DotNetLibraryAssetKind.Runtime &&
element.TryGetProperty("fileVersion", out var fileVersionElement) &&
fileVersionElement.ValueKind == JsonValueKind.String)
{
fileVersion = NormalizeValue(fileVersionElement.GetString());
}
string? runtimeIdentifier = rid;
if (element.TryGetProperty("rid", out var ridElement) && ridElement.ValueKind == JsonValueKind.String)
{
runtimeIdentifier = NormalizeValue(ridElement.GetString());
}
asset = new DotNetLibraryAsset(
RelativePath: normalizedPath,
TargetFramework: NormalizeValue(tfm),
RuntimeIdentifier: NormalizeValue(runtimeIdentifier),
AssemblyVersion: assemblyVersion,
FileVersion: fileVersion,
Kind: kind);
return true;
}
private static string NormalizePath(string value)
{
var normalized = NormalizeValue(value);
if (string.IsNullOrEmpty(normalized))
{
return string.Empty;
}
return normalized.Replace('\\', '/');
}
private static bool IsManagedAssembly(string path)
=> path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase);
private static string? NormalizeValue(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return value.Trim();
}
}

View File

@@ -0,0 +1,332 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Xml;
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal;
internal static class DotNetFileMetadataCache
{
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<string>> Sha256Cache = new();
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<AssemblyName>> AssemblyCache = new();
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<FileVersionInfo>> VersionCache = new();
public static bool TryGetSha256(string path, out string? sha256)
=> TryGet(path, Sha256Cache, ComputeSha256, out sha256);
public static bool TryGetAssemblyName(string path, out AssemblyName? assemblyName)
=> TryGet(path, AssemblyCache, TryReadAssemblyName, out assemblyName);
public static bool TryGetFileVersionInfo(string path, out FileVersionInfo? versionInfo)
=> TryGet(path, VersionCache, TryReadFileVersionInfo, out versionInfo);
private static bool TryGet<T>(string path, ConcurrentDictionary<DotNetFileCacheKey, Optional<T>> cache, Func<string, T?> resolver, out T? value)
where T : class
{
value = null;
DotNetFileCacheKey key;
try
{
var info = new FileInfo(path);
if (!info.Exists)
{
return false;
}
key = new DotNetFileCacheKey(info.FullName, info.Length, info.LastWriteTimeUtc.Ticks);
}
catch (IOException)
{
return false;
}
catch (UnauthorizedAccessException)
{
return false;
}
catch (SecurityException)
{
return false;
}
catch (ArgumentException)
{
return false;
}
catch (NotSupportedException)
{
return false;
}
var optional = cache.GetOrAdd(key, static (cacheKey, state) => CreateOptional(cacheKey.Path, state.resolver), (resolver, path));
if (!optional.HasValue)
{
return false;
}
value = optional.Value;
return value is not null;
}
private static Optional<T> CreateOptional<T>(string path, Func<string, T?> resolver) where T : class
{
try
{
var value = resolver(path);
return Optional<T>.From(value);
}
catch (FileNotFoundException)
{
return Optional<T>.None;
}
catch (FileLoadException)
{
return Optional<T>.None;
}
catch (BadImageFormatException)
{
return Optional<T>.None;
}
catch (UnauthorizedAccessException)
{
return Optional<T>.None;
}
catch (SecurityException)
{
return Optional<T>.None;
}
catch (IOException)
{
return Optional<T>.None;
}
}
private static string? ComputeSha256(string path)
{
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
using var sha = System.Security.Cryptography.SHA256.Create();
var hash = sha.ComputeHash(stream);
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static AssemblyName? TryReadAssemblyName(string path)
{
try
{
return AssemblyName.GetAssemblyName(path);
}
catch (FileNotFoundException)
{
return null;
}
catch (FileLoadException)
{
return null;
}
catch (BadImageFormatException)
{
return null;
}
catch (IOException)
{
return null;
}
}
private static FileVersionInfo? TryReadFileVersionInfo(string path)
{
try
{
return FileVersionInfo.GetVersionInfo(path);
}
catch (FileNotFoundException)
{
return null;
}
catch (IOException)
{
return null;
}
catch (UnauthorizedAccessException)
{
return null;
}
}
}
internal static class DotNetLicenseCache
{
private static readonly ConcurrentDictionary<DotNetFileCacheKey, Optional<DotNetLicenseInfo>> Licenses = new();
public static bool TryGetLicenseInfo(string nuspecPath, out DotNetLicenseInfo? info)
{
info = null;
DotNetFileCacheKey key;
try
{
var fileInfo = new FileInfo(nuspecPath);
if (!fileInfo.Exists)
{
return false;
}
key = new DotNetFileCacheKey(fileInfo.FullName, fileInfo.Length, fileInfo.LastWriteTimeUtc.Ticks);
}
catch (IOException)
{
return false;
}
catch (UnauthorizedAccessException)
{
return false;
}
catch (SecurityException)
{
return false;
}
var optional = Licenses.GetOrAdd(key, static (cacheKey, path) => CreateOptional(path), nuspecPath);
if (!optional.HasValue)
{
return false;
}
info = optional.Value;
return info is not null;
}
private static Optional<DotNetLicenseInfo> CreateOptional(string nuspecPath)
{
try
{
var info = Parse(nuspecPath);
return Optional<DotNetLicenseInfo>.From(info);
}
catch (IOException)
{
return Optional<DotNetLicenseInfo>.None;
}
catch (UnauthorizedAccessException)
{
return Optional<DotNetLicenseInfo>.None;
}
catch (SecurityException)
{
return Optional<DotNetLicenseInfo>.None;
}
catch (XmlException)
{
return Optional<DotNetLicenseInfo>.None;
}
}
private static DotNetLicenseInfo? Parse(string path)
{
using var stream = File.OpenRead(path);
using var reader = XmlReader.Create(stream, new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Ignore,
IgnoreComments = true,
IgnoreWhitespace = true,
});
var expressions = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
var files = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
var urls = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
while (reader.Read())
{
if (reader.NodeType != XmlNodeType.Element)
{
continue;
}
if (string.Equals(reader.LocalName, "license", StringComparison.OrdinalIgnoreCase))
{
var type = reader.GetAttribute("type");
var value = reader.ReadElementContentAsString()?.Trim();
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
if (string.Equals(type, "expression", StringComparison.OrdinalIgnoreCase))
{
expressions.Add(value);
}
else if (string.Equals(type, "file", StringComparison.OrdinalIgnoreCase))
{
files.Add(NormalizeLicensePath(value));
}
else
{
expressions.Add(value);
}
}
else if (string.Equals(reader.LocalName, "licenseUrl", StringComparison.OrdinalIgnoreCase))
{
var value = reader.ReadElementContentAsString()?.Trim();
if (!string.IsNullOrWhiteSpace(value))
{
urls.Add(value);
}
}
}
if (expressions.Count == 0 && files.Count == 0 && urls.Count == 0)
{
return null;
}
return new DotNetLicenseInfo(
expressions.ToArray(),
files.ToArray(),
urls.ToArray());
}
private static string NormalizeLicensePath(string value)
=> value.Replace('\\', '/').Trim();
}
internal sealed record DotNetLicenseInfo(
IReadOnlyList<string> Expressions,
IReadOnlyList<string> Files,
IReadOnlyList<string> Urls);
internal readonly record struct DotNetFileCacheKey(string Path, long Length, long LastWriteTicks)
{
private readonly string _normalizedPath = OperatingSystem.IsWindows()
? Path.ToLowerInvariant()
: Path;
public bool Equals(DotNetFileCacheKey other)
=> Length == other.Length
&& LastWriteTicks == other.LastWriteTicks
&& string.Equals(_normalizedPath, other._normalizedPath, StringComparison.Ordinal);
public override int GetHashCode()
=> HashCode.Combine(_normalizedPath, Length, LastWriteTicks);
}
internal readonly struct Optional<T> where T : class
{
private Optional(bool hasValue, T? value)
{
HasValue = hasValue;
Value = value;
}
public bool HasValue { get; }
public T? Value { get; }
public static Optional<T> From(T? value)
=> value is null ? None : new Optional<T>(true, value);
public static Optional<T> None => default;
}

View File

@@ -0,0 +1,158 @@
using System.Linq;
using System.Text.Json;
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal;
internal sealed class DotNetRuntimeConfig
{
private DotNetRuntimeConfig(
string relativePath,
IReadOnlyCollection<string> tfms,
IReadOnlyCollection<string> frameworks,
IReadOnlyCollection<RuntimeGraphEntry> runtimeGraph)
{
RelativePath = relativePath;
Tfms = tfms;
Frameworks = frameworks;
RuntimeGraph = runtimeGraph;
}
public string RelativePath { get; }
public IReadOnlyCollection<string> Tfms { get; }
public IReadOnlyCollection<string> Frameworks { get; }
public IReadOnlyCollection<RuntimeGraphEntry> RuntimeGraph { get; }
public static DotNetRuntimeConfig? Load(string absolutePath, string relativePath, CancellationToken cancellationToken)
{
using var stream = File.OpenRead(absolutePath);
using var document = JsonDocument.Parse(stream, new JsonDocumentOptions
{
AllowTrailingCommas = true,
CommentHandling = JsonCommentHandling.Skip
});
var root = document.RootElement;
if (!root.TryGetProperty("runtimeOptions", out var runtimeOptions) || runtimeOptions.ValueKind is not JsonValueKind.Object)
{
return null;
}
var tfms = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
var frameworks = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
var runtimeGraph = new List<RuntimeGraphEntry>();
if (runtimeOptions.TryGetProperty("tfm", out var tfmElement) && tfmElement.ValueKind == JsonValueKind.String)
{
AddIfPresent(tfms, tfmElement.GetString());
}
if (runtimeOptions.TryGetProperty("framework", out var frameworkElement) && frameworkElement.ValueKind == JsonValueKind.Object)
{
var frameworkId = FormatFramework(frameworkElement);
AddIfPresent(frameworks, frameworkId);
}
if (runtimeOptions.TryGetProperty("frameworks", out var frameworksElement) && frameworksElement.ValueKind == JsonValueKind.Array)
{
foreach (var item in frameworksElement.EnumerateArray())
{
cancellationToken.ThrowIfCancellationRequested();
var frameworkId = FormatFramework(item);
AddIfPresent(frameworks, frameworkId);
}
}
if (runtimeOptions.TryGetProperty("includedFrameworks", out var includedElement) && includedElement.ValueKind == JsonValueKind.Array)
{
foreach (var item in includedElement.EnumerateArray())
{
cancellationToken.ThrowIfCancellationRequested();
var frameworkId = FormatFramework(item);
AddIfPresent(frameworks, frameworkId);
}
}
if (runtimeOptions.TryGetProperty("runtimeGraph", out var runtimeGraphElement) &&
runtimeGraphElement.ValueKind == JsonValueKind.Object &&
runtimeGraphElement.TryGetProperty("runtimes", out var runtimesElement) &&
runtimesElement.ValueKind == JsonValueKind.Object)
{
foreach (var ridProperty in runtimesElement.EnumerateObject())
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(ridProperty.Name))
{
continue;
}
var fallbacks = new List<string>();
if (ridProperty.Value.ValueKind == JsonValueKind.Object &&
ridProperty.Value.TryGetProperty("fallbacks", out var fallbacksElement) &&
fallbacksElement.ValueKind == JsonValueKind.Array)
{
foreach (var fallback in fallbacksElement.EnumerateArray())
{
if (fallback.ValueKind == JsonValueKind.String)
{
var fallbackValue = fallback.GetString();
if (!string.IsNullOrWhiteSpace(fallbackValue))
{
fallbacks.Add(fallbackValue.Trim());
}
}
}
}
runtimeGraph.Add(new RuntimeGraphEntry(ridProperty.Name.Trim(), fallbacks));
}
}
return new DotNetRuntimeConfig(
relativePath,
tfms.ToArray(),
frameworks.ToArray(),
runtimeGraph);
}
private static void AddIfPresent(ISet<string> set, string? value)
{
if (!string.IsNullOrWhiteSpace(value))
{
set.Add(value.Trim());
}
}
private static string? FormatFramework(JsonElement element)
{
if (element.ValueKind is not JsonValueKind.Object)
{
return null;
}
var name = element.TryGetProperty("name", out var nameElement) && nameElement.ValueKind == JsonValueKind.String
? nameElement.GetString()
: null;
var version = element.TryGetProperty("version", out var versionElement) && versionElement.ValueKind == JsonValueKind.String
? versionElement.GetString()
: null;
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
if (string.IsNullOrWhiteSpace(version))
{
return name.Trim();
}
return $"{name.Trim()}@{version.Trim()}";
}
internal sealed record RuntimeGraphEntry(string Rid, IReadOnlyList<string> Fallbacks);
}