File: WorkloadSet.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.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 Microsoft.DotNet.MSBuildSdkResolver;
using Strings = Microsoft.NET.Sdk.Localization.Strings;
 
namespace Microsoft.NET.Sdk.WorkloadManifestReader
{
    public class WorkloadSet
    {
        public Dictionary<ManifestId, (ManifestVersion Version, SdkFeatureBand FeatureBand)> ManifestVersions = new();
 
        //  TODO: Generate version from hash of manifest versions if not otherwise set
        public string? Version { get; set; }
 
        //  Indicates that a workload set is a baseline workload set that was installed with the .NET SDK.
        //  It should not be subject to normal garbage collection unless the SDK that installed it is removed
        public bool IsBaselineWorkloadSet { get; set; }
 
        public static WorkloadSet FromManifests(IEnumerable<WorkloadManifestInfo> manifests)
        {
            return new WorkloadSet()
            {
                ManifestVersions = manifests.ToDictionary(m => new ManifestId(m.Id), m => (new ManifestVersion(m.Version), new SdkFeatureBand(m.ManifestFeatureBand)))
            };
        }
 
        public static WorkloadSet FromDictionaryForJson(IDictionary<string, string?> dictionary, SdkFeatureBand defaultFeatureBand)
        {
            var manifestVersions = dictionary
                .Select(manifest =>
                {
                    ManifestVersion manifestVersion;
                    SdkFeatureBand manifestFeatureBand;
                    var parts = manifest.Value?.Split('/');
 
                    string manifestVersionString = string.Empty;
                    if (parts != null)
                    {
                        manifestVersionString = parts[0];
                    }
                    if (!FXVersion.TryParse(manifestVersionString, out FXVersion? version))
                    {
                        throw new FormatException(string.Format(Strings.InvalidVersionForWorkload, manifest.Key, manifestVersionString));
                    }
 
                    manifestVersion = new ManifestVersion(parts?[0]);
                    if (parts != null && parts.Length == 1)
                    {
                        manifestFeatureBand = defaultFeatureBand;
                    }
                    else
                    {
                        manifestFeatureBand = new SdkFeatureBand(parts?[1]);
                    }
                    return (id: new ManifestId(manifest.Key), manifestVersion, manifestFeatureBand);
                }).ToDictionary(t => t.id, t => (t.manifestVersion, t.manifestFeatureBand));
 
            return new WorkloadSet()
            {
                ManifestVersions = manifestVersions
            };
        }
 
        public static WorkloadSet FromJson(string json, SdkFeatureBand defaultFeatureBand)
        {
            var jsonSerializerOptions = new JsonSerializerOptions()
            {
                AllowTrailingCommas = true,
                ReadCommentHandling = JsonCommentHandling.Skip
            };
            return FromDictionaryForJson(JsonSerializer.Deserialize<IDictionary<string, string>>(json, jsonSerializerOptions)!, defaultFeatureBand);
        }
 
        public static WorkloadSet? FromWorkloadSetFolder(string path, string workloadSetVersion, SdkFeatureBand defaultFeatureBand)
        {
            WorkloadSet? workloadSet = null;
            foreach (var jsonFile in Directory.GetFiles(path, "*.workloadset.json"))
            {
                var newWorkloadSet = WorkloadSet.FromJson(File.ReadAllText(jsonFile), defaultFeatureBand);
                if (workloadSet == null)
                {
                    workloadSet = newWorkloadSet;
                }
                else
                {
                    //  If there are multiple workloadset.json files, merge them
                    foreach (var kvp in newWorkloadSet.ManifestVersions)
                    {
                        if (workloadSet.ManifestVersions.ContainsKey(kvp.Key))
                        {
                            throw new InvalidOperationException($"Workload set files in {path} defined the same manifest ({kvp.Key}) multiple times");
                        }
                        workloadSet.ManifestVersions.Add(kvp.Key, kvp.Value);
                    }
                }
            }
 
            if (workloadSet == null)
            {
                return null;
            }
 
            if (File.Exists(Path.Combine(path, "baseline.workloadset.json")))
            {
                workloadSet.IsBaselineWorkloadSet = true;
            }
 
            workloadSet.Version = workloadSetVersion;
 
            return workloadSet;
        }
 
        public Dictionary<string, string> ToDictionaryForJson()
        {
            var dictionary = ManifestVersions.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Version + "/" + kvp.Value.FeatureBand, StringComparer.OrdinalIgnoreCase);
            return dictionary;
        }
 
        public string ToJson()
        {
            var json = JsonSerializer.Serialize(ToDictionaryForJson(), new JsonSerializerOptions() { WriteIndented = true });
            return json;
        }
 
        //  Corresponding method for opposite direction is in WorkloadManifestUpdater, as its implementation depends on NuGetVersion,
        //  which we'd like to avoid adding as a dependency here.
        public static string WorkloadSetVersionToWorkloadSetPackageVersion(string setVersion, out SdkFeatureBand sdkFeatureBand)
        {
            string[] sections = setVersion.Split(new char[] { '-', '+' }, 2);
            string versionCore = sections[0];
            string? preReleaseOrBuild = sections.Length > 1 ? sections[1] : null;
 
            string[] coreComponents = versionCore.Split('.');
            string major = coreComponents[0];
            string minor = coreComponents[1];
            string patch = coreComponents[2];
 
            string packageVersion = $"{major}.{patch}.";
            if (coreComponents.Length == 3)
            {
                //  No workload set patch version
                packageVersion += "0";
 
                //  Use preview specifier (if any) from workload set version as part of SDK feature band
                sdkFeatureBand = new SdkFeatureBand(setVersion);
            }
            else
            {
                //  Workload set version has workload patch version (ie 4 components)
                packageVersion += coreComponents[3];
 
                //  Don't include any preview specifiers in SDK feature band
                sdkFeatureBand = new SdkFeatureBand($"{major}.{minor}.{patch}");
            }
 
            if (preReleaseOrBuild != null)
            {
                //  Figure out if we split on a '-' or '+'
                char separator = setVersion[sections[0].Length];
                packageVersion += separator + preReleaseOrBuild;
            }
 
            return packageVersion;
        }
 
        public static SdkFeatureBand GetWorkloadSetFeatureBand(string setVersion)
        {
            WorkloadSetVersionToWorkloadSetPackageVersion(setVersion, out SdkFeatureBand sdkFeatureBand);
            return sdkFeatureBand;
        }
    }
}