File: src\CalculateAssemblyAndFileVersions.cs
Web Access
Project: 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>
[MSBuildMultiThreadableTask]
public class CalculateAssemblyAndFileVersions : 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";
        }
    }
}