File: ShowMissingWorkloads.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.
 
#nullable disable
 
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Configurer;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using static Microsoft.NET.Sdk.WorkloadManifestReader.WorkloadResolver;
 
namespace Microsoft.NET.Build.Tasks
{
    public class ShowMissingWorkloads : TaskBase
    {
        private static readonly string MauiCrossPlatTopLevelVSWorkloads = "Microsoft.VisualStudio.Workload.NetCrossPlat";
        private static readonly string MauiComponentGroupVSWorkload = "Microsoft.VisualStudio.ComponentGroup.Maui.All";
        private static readonly string WasmTopLevelVSWorkload = "Microsoft.VisualStudio.Workload.NetWeb";
        private static readonly HashSet<string> MauiWorkloadIds = new(StringComparer.OrdinalIgnoreCase)
            { "android", "android-aot", "ios", "maccatalyst", "macos", "maui", "maui-android",
            "maui-desktop", "maui-ios", "maui-maccatalyst", "maui-mobile", "maui-windows", "tvos" };
        private static readonly HashSet<string> WasmWorkloadIds = new(StringComparer.OrdinalIgnoreCase)
            { "wasm-tools", "wasm-tools-net6", "wasm-tools-net7", "wasm-tools-net8", "wasm-tools-net9" };
 
        public ITaskItem[] MissingWorkloadPacks { get; set; }
 
        public string NetCoreRoot { get; set; }
 
        public string NETCoreSdkVersion { get; set; }
 
        public bool GenerateErrorsForMissingWorkloads { get; set; }
 
        [Output]
        public ITaskItem[] SuggestedWorkloads { get; set; }
 
        protected override void ExecuteCore()
        {
            if (MissingWorkloadPacks.Any())
            {
                string userProfileDir = CliFolderPathCalculatorCore.GetDotnetUserProfileFolderPath();
 
                //  When running MSBuild tasks, the current directory is always the project directory, so we can use that as the
                //  starting point to search for global.json
                string globalJsonPath = SdkDirectoryWorkloadManifestProvider.GetGlobalJsonPath(Environment.CurrentDirectory);
 
                var workloadManifestProvider = new SdkDirectoryWorkloadManifestProvider(NetCoreRoot, NETCoreSdkVersion, userProfileDir, globalJsonPath);
                var workloadResolver = Create(workloadManifestProvider, NetCoreRoot, NETCoreSdkVersion, userProfileDir);
 
                var suggestedWorkloads = workloadResolver.GetWorkloadSuggestionForMissingPacks(
                    MissingWorkloadPacks.Select(item => new WorkloadPackId(item.ItemSpec)).ToList(),
                    out ISet<WorkloadPackId> unsatisfiablePacks
                );
 
                if (GenerateErrorsForMissingWorkloads)
                {
                    if (suggestedWorkloads is not null)
                    {
                        var errorMessage = string.Format(CultureInfo.CurrentCulture,
                            Strings.WorkloadNotInstalled, string.Join(" ", suggestedWorkloads.Select(w => w.Id)));
                        Log.LogError(errorMessage);
                    }
                    else
                    {
                        Log.LogError(Strings.WorkloadNotAvailable, string.Join(" ", unsatisfiablePacks));
                    }
                }
 
                if (suggestedWorkloads is not null)
                {
                    SuggestedWorkloads = suggestedWorkloads.Select(suggestedWorkload =>
                    {
                        var suggestedWorkloadsList = GetSuggestedWorkloadsList(suggestedWorkload);
                        var taskItem = new TaskItem(suggestedWorkload.Id);
                        taskItem.SetMetadata("VisualStudioComponentId", ToSafeId(suggestedWorkload.Id));
                        taskItem.SetMetadata("VisualStudioComponentIds", string.Join(";", suggestedWorkloadsList));
                        return taskItem;
                    }).ToArray();
                }
            }
        }
 
        internal static string ToSafeId(string id)
        {
            return id.Replace("-", ".").Replace(" ", ".").Replace("_", ".");
        }
 
        private static IEnumerable<string> GetSuggestedWorkloadsList(WorkloadInfo workloadInfo)
        {
            yield return ToSafeId(workloadInfo.Id);
            if (MauiWorkloadIds.Contains(workloadInfo.Id.ToString()))
            {
                yield return MauiCrossPlatTopLevelVSWorkloads;
                yield return MauiComponentGroupVSWorkload;
            }
            if (WasmWorkloadIds.Contains(workloadInfo.Id.ToString()))
            {
                yield return WasmTopLevelVSWorkload;
            }
        }
    }
}