File: src\CalculateAssemblyAndFileVersions.cs
Web Access
Project: src\src\Microsoft.DotNet.Arcade.Sdk\Microsoft.DotNet.Arcade.Sdk.csproj (Microsoft.DotNet.Arcade.Sdk)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Versioning;
 
namespace Microsoft.DotNet.Arcade.Sdk
{
    /// <summary>
    /// File version has 4 parts and need to increase every official build. This is especially important when building MSIs.
    /// See https://github.com/dotnet/arcade/blob/master/Documentation/CorePackages/Versioning.md#assembly-version.
    /// </summary>
    public class CalculateAssemblyAndFileVersions : Microsoft.Build.Utilities.Task
    {
        private const int MaxMinor = 654;
        private const int MaxBuild = 9999;
 
        [Required]
        public string VersionPrefix { get; set; }
 
        [Required]
        public string BuildNumber { get; set; }
 
        public int PatchNumber { get; set; }
 
        public bool AutoGenerateAssemblyVersion { get; set; }
 
        [Output]
        public string AssemblyVersion { get; private set; }
 
        [Output]
        public string FileVersion { get; private set; }
 
        public override bool Execute()
        {
            ExecuteImpl();
            return !Log.HasLoggedErrors;
        }
 
        private void ExecuteImpl()
        {
            if (!Version.TryParse(VersionPrefix, out var prefix) ||
                prefix.Major == -1 ||
                prefix.Minor == -1 ||
                prefix.Build == -1 ||
                prefix.Revision != -1)
            {
                Log.LogError($"VersionPrefix is not a valid 3-part version: {VersionPrefix}");
                return;
            }
 
            if (PatchNumber < 0)
            {
                Log.LogError("Invalid value of VersionBaseShortDate");
                return;
            }
 
            if (AutoGenerateAssemblyVersion)
            {
                int assemblyMajor = prefix.Major;
                int assemblyMinor = prefix.Minor;
                int assemblyPatch = PatchNumber / 50000;
                int assemblyRevision = PatchNumber % 50000;
 
                FileVersion = AssemblyVersion = $"{assemblyMajor}.{assemblyMinor}.{assemblyPatch}.{assemblyRevision}";
            }
            else
            {
                if (prefix.Minor > MaxMinor)
                {
                    Log.LogError($"The minor version specified in {nameof(VersionPrefix)} must be at most {MaxMinor}: {VersionPrefix}");
                    return;
                }
 
                if (prefix.Build > MaxBuild)
                {
                    Log.LogError($"The patch version specified in {nameof(VersionPrefix)} must be at most {MaxBuild}: {VersionPrefix}");
                    return;
                }
 
                int yy, mm, dd, r;
                try
                {
                    yy = int.Parse(BuildNumber.Substring(2, 2));
                    mm = int.Parse(BuildNumber.Substring(4, 2));
                    dd = int.Parse(BuildNumber.Substring(6, 2));
                    r = int.Parse(BuildNumber.Substring(9));
                }
                catch
                {
                    yy = mm = dd = r = -1;
                }
 
                if (yy < 0 || yy > 99 || mm < 1 || mm > 12 || dd < 1 || dd > 31 || r < 0 || r > 199)
                {
                    Log.LogError($"Invalid format of {nameof(BuildNumber)}: {BuildNumber}");
                    return;
                }
 
                int fileMajor = prefix.Major;
                int fileMinor = prefix.Minor * 100 + prefix.Build / 100;
                int filePatch = (prefix.Build % 100) * 100 + yy;
                int fileRevision = mm * 5000 + dd * 100 + r;
 
                FileVersion = $"{fileMajor}.{fileMinor}.{filePatch}.{fileRevision}";
                AssemblyVersion = $"{prefix}.0";
            }
        }
    }
}