File: ProcessRuntimeAnalyzerVersions.cs
Web Access
Project: src\src\sdk\src\Tasks\sdk-tasks\sdk-tasks.csproj (sdk-tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.DotNet.Build.Tasks;

/// <summary>
/// Extracts version numbers and saves them into a metadata file.
/// </summary>
public sealed class ProcessRuntimeAnalyzerVersions : Task
{
    [Required]
    public ITaskItem[]? Inputs { get; set; }

    [Required]
    public string? MetadataFilePath { get; set; }

    [Output]
    public ITaskItem[]? Outputs { get; set; }

    public override bool Execute()
    {
        var metadata = new Dictionary<string, MetadataEntry>();

        // Extract version from a path like:
        //   ...\packs\Microsoft.NetCore.App.Ref\<version>\analyzers\**\*.*
        // The version segment is always the first segment in %(RecursiveDir).
        foreach (var input in Inputs ?? [])
        {
            var deploymentSubpath = input.GetMetadata("DeploymentSubpath");
            var recursiveDir = input.GetMetadata("CustomRecursiveDir").Replace('/', '\\');

            var slashIndex = recursiveDir.IndexOf('\\');
            var version = recursiveDir.Substring(0, slashIndex);
            var rest = recursiveDir.Substring(slashIndex + 1);

            input.SetMetadata("CustomRecursiveDir", rest);

            if (!metadata.TryGetValue(deploymentSubpath, out var entry))
            {
                entry = new MetadataEntry(version);
                metadata.Add(deploymentSubpath, entry);
            }
            else if (entry.Version != version)
            {
                Log.LogError($"Version mismatch for '{deploymentSubpath}': '{entry.Version}' != '{version}'. " +
                    $"Expected only one version of '{input.GetMetadata("Identity")}'.");
                return false;
            }

            if (!rest.EndsWith("\\"))
            {
                rest += '\\';
            }

            entry.Files.Add($"{rest}{input.GetMetadata("Filename")}{input.GetMetadata("Extension")}");
        }

        Directory.CreateDirectory(Path.GetDirectoryName(MetadataFilePath)!);
        File.WriteAllText(path: MetadataFilePath!, JsonSerializer.Serialize(metadata, SdkTasksJsonSerializerContext.Default.DictionaryStringMetadataEntry));

        Outputs = Inputs;
        return true;
    }

    internal sealed class MetadataEntry(string version)
    {
        public string Version { get; } = version;
        public List<string> Files { get; } = [];
    }
}

[JsonSerializable(typeof(Dictionary<string, ProcessRuntimeAnalyzerVersions.MetadataEntry>))]
internal partial class SdkTasksJsonSerializerContext : JsonSerializerContext;