File: CalculateTemplateVersions.cs
Web Access
Project: ..\..\..\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.
 
#nullable disable
 
using NuGet.Versioning;
 
namespace Microsoft.DotNet.Build.Tasks
{
    public class CalculateTemplateVersions : Task
    {
        //  Group BundledTemplates by TemplateFrameworkVersion
        //  In each group, get the version of the template with UseVersionForTemplateInstallPath=true
        //  From that version number, get the BundledTemplateInstallPath, BundledTemplateMajorMinorVersion, and BundledTemplateMajorMinorPatchVersion
        [Required]
        public ITaskItem [] BundledTemplates { get; set; }
 
        [Required]
        public string FullNugetVersion { get; set; }
 
        [Required]
        public string ProductMonikerRid { get; set; }
 
        public string InstallerExtension { get; set; }
 
        [Required]
        public int CombinedBuildNumberAndRevision { get; set; }
 
        //  Should be the BundledTemplates with BundledTemplateInstallPath metadata set to the value calculated for that group
        [Output]
        public ITaskItem [] BundledTemplatesWithInstallPaths { get; set; }
 
        //  For each group of templates (grouped by TemplateFrameworkVersion), this should be the following
        //  ItemSpec: NetCore60Templates
        //  TemplateBaseFilename: dotnet-60templates
        //  TemplatesMajorMinorVersion: 6.0 (from BundledTemplateMajorMinorVersion from group)
        //  InstallerUpgradeCode: Guid generated using GenerateGuidFromName, combining TemplateBaseFilename, FullNugetVersion, ProductMonikerRid, and InstallerExtension
        //  MSIVersion: Result of calling GenerateMsiVersionFromFullVersion logic with CombinedBuildNumberAndRevision and BundledTemplateMajorMinorPatchVersion from template group
        [Output]
        public ITaskItem [] TemplatesComponents { get; set; }
 
        private const int PatchVersionResetOffset = 1;
 
        public override bool Execute()
        {
            var groups = BundledTemplates.GroupBy(bt => bt.GetMetadata("TemplateFrameworkVersion"))
                .ToDictionary(g => g.Key, g =>
                {
                    var itemWithVersion = g.SingleOrDefault(i => i.GetMetadata("UseVersionForTemplateInstallPath").Equals("true", StringComparison.OrdinalIgnoreCase));
                    if (itemWithVersion == null)
                    {
                        throw new InvalidOperationException("Could not find single item with UseVersionForTemplateInstallPath for templates with TemplateFrameworkVersion: " + g.Key);
                    }
 
                    return Calculate(itemWithVersion.GetMetadata("PackageVersion"));
                });
 
            BundledTemplatesWithInstallPaths = BundledTemplates.Select(t =>
            {
                var templateWithInstallPath = new TaskItem(t);
                templateWithInstallPath.SetMetadata("BundledTemplateInstallPath", groups[t.GetMetadata("TemplateFrameworkVersion")].InstallPath);
                return templateWithInstallPath;
            }).ToArray();
 
            TemplatesComponents = groups.Select(g =>
            {
                string majorMinorWithoutDots = g.Value.MajorMinorVersion.Replace(".", "");
                var componentItem = new TaskItem($"NetCore{majorMinorWithoutDots}Templates");
                var templateBaseFilename = $"dotnet-{majorMinorWithoutDots}templates";
                componentItem.SetMetadata("TemplateBaseFilename", templateBaseFilename);
                componentItem.SetMetadata("TemplatesMajorMinorVersion", g.Value.MajorMinorVersion);
                var installerUpgradeCode = GenerateGuidFromName.GenerateGuid(string.Join("-", templateBaseFilename, FullNugetVersion, ProductMonikerRid) + InstallerExtension).ToString().ToUpper();
                componentItem.SetMetadata("InstallerUpgradeCode", installerUpgradeCode);
                componentItem.SetMetadata("MSIVersion", GenerateMsiVersionFromFullVersion.GenerateMsiVersion(CombinedBuildNumberAndRevision, g.Value.MajorMinorPatchVersion));
 
                var brandName = System.Version.Parse(g.Key).Major >= 5 ?
                    $"Microsoft .NET {g.Key} Templates" :
                    $"Microsoft .NET Core {g.Key} Templates";
 
                componentItem.SetMetadata("BrandNameWithoutVersion", brandName);
 
                return componentItem;
            }).ToArray();
 
            return true;
        }
 
        public static BundledTemplate Calculate(string aspNetCorePackageVersionTemplate)
        {
            var aspNetCoreTemplate = NuGetVersion.Parse(aspNetCorePackageVersionTemplate);
            NuGetVersion baseMajorMinorPatch = GetBaseMajorMinorPatch(aspNetCoreTemplate);
            string bundledTemplateInstallPath = aspNetCoreTemplate.IsPrerelease
                ? $"{baseMajorMinorPatch.Major}.{baseMajorMinorPatch.Minor}.{baseMajorMinorPatch.Patch}-{aspNetCoreTemplate.Release}"
                : $"{baseMajorMinorPatch.Major}.{baseMajorMinorPatch.Minor}.{baseMajorMinorPatch.Patch}";
 
            return new BundledTemplate
            {
                InstallPath = bundledTemplateInstallPath,
                MajorMinorVersion = $"{baseMajorMinorPatch.Major}.{baseMajorMinorPatch.Minor}",
                MajorMinorPatchVersion = $"{baseMajorMinorPatch.Major}.{baseMajorMinorPatch.Minor}.{baseMajorMinorPatch.Patch}"
            };
        }
 
        private static NuGetVersion GetBaseMajorMinorPatch(NuGetVersion aspNetCoreTemplate)
        {
            // due to historical bug https://github.com/dotnet/core-sdk/issues/6243
            // we need to increase patch version by one in order to "reset" existing install ComponentId
            // more in the above bug's detail.
            // There is no non-deterministic existing ComponentId under Major version 5.
            // so only apply the patch bump when below 5
            int basePatch = aspNetCoreTemplate.Major < 5 ?
                aspNetCoreTemplate.Patch + PatchVersionResetOffset :
                aspNetCoreTemplate.Patch;
            return new NuGetVersion(aspNetCoreTemplate.Major, aspNetCoreTemplate.Minor, basePatch);
        }
    }
 
    public class BundledTemplate
    {
        public string InstallPath { get; set; }
        public string MajorMinorVersion { get; set; }
        public string MajorMinorPatchVersion { get; set; }
    }
}