using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace DotNetOpenAuth.BuildTasks { public class GetBuildVersion : Task { /// /// Initializes a new instance of the class. /// public GetBuildVersion() { } /// /// Gets the version string to use in the compiled assemblies. /// [Output] public string Version { get; private set; } /// /// Gets the version string to use in the official release name (lacks revision number). /// [Output] public string SimpleVersion { get; private set; } /// /// Gets or sets the major.minor version string. /// /// /// The x.y string (no build number or revision number). /// [Output] public string MajorMinorVersion { get; set; } /// /// Gets or sets the prerelease version, or empty if this is a final release. /// /// /// The prerelease version. /// [Output] public string PrereleaseVersion { get; set; } /// /// Gets or sets the version string to use for NuGet packages containing OAuth 2 components. /// [Output] public string OAuth2PackagesVersion { get; set; } /// /// Gets the Git revision control commit id for HEAD (the current source code version). /// [Output] public string GitCommitId { get; private set; } /// /// Gets the build number (JDate) for this version. /// [Output] public int BuildNumber { get; private set; } /// /// The file that contains the version base (Major.Minor.Build) to use. /// [Required] public string VersionFile { get; set; } /// /// Gets or sets the parent directory of the .git directory. /// public string GitRepoRoot { get; set; } public override bool Execute() { try { Version typedVersion; string prerelease, oauth2PackagesVersion; this.ReadVersionFromFile(out typedVersion, out prerelease, out oauth2PackagesVersion); this.PrereleaseVersion = prerelease; this.OAuth2PackagesVersion = oauth2PackagesVersion; this.SimpleVersion = typedVersion.ToString(); this.MajorMinorVersion = new Version(typedVersion.Major, typedVersion.Minor).ToString(); this.BuildNumber = this.CalculateJDate(DateTime.Now); var fullVersion = new Version(typedVersion.Major, typedVersion.Minor, typedVersion.Build, this.BuildNumber); this.Version = fullVersion.ToString(); this.GitCommitId = GetGitHeadCommitId(); } catch (ArgumentOutOfRangeException ex) { Log.LogErrorFromException(ex); return false; } return true; } private string GetGitHeadCommitId() { if (string.IsNullOrEmpty(this.GitRepoRoot)) { return string.Empty; } string commitId = string.Empty; // First try asking Git for the HEAD commit id try { string cmdPath = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"); var psi = new ProcessStartInfo(cmdPath, "/c git rev-parse HEAD") { WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, RedirectStandardOutput = true, UseShellExecute = false }; var git = Process.Start(psi); commitId = git.StandardOutput.ReadLine(); git.WaitForExit(); if (git.ExitCode != 0) { commitId = string.Empty; } if (commitId != null) { commitId = commitId.Trim(); if (commitId.Length == 40) { return commitId; } } } catch (InvalidOperationException) { } catch (Win32Exception) { } // Failing being able to use the git command to figure out the HEAD commit ID, try the filesystem directly. try { string headContent = File.ReadAllText(Path.Combine(this.GitRepoRoot, @".git/HEAD")).Trim(); if (headContent.StartsWith("ref:", StringComparison.Ordinal)) { string refName = headContent.Substring(5).Trim(); string refPath = Path.Combine(this.GitRepoRoot, ".git/" + refName); if (File.Exists(refPath)) { commitId = File.ReadAllText(refPath).Trim(); } else { string packedRefPath = Path.Combine(this.GitRepoRoot, ".git/packed-refs"); string matchingLine = File.ReadAllLines(packedRefPath).FirstOrDefault(line => line.EndsWith(refName)); if (matchingLine != null) { commitId = matchingLine.Substring(0, matchingLine.IndexOf(' ')); } } } else { commitId = headContent; } } catch (FileNotFoundException) { } catch (DirectoryNotFoundException) { } commitId = commitId ?? String.Empty; // doubly-be sure it's not null, since in some error cases it can be. return commitId.Trim(); } private void ReadVersionFromFile(out Version typedVersion, out string prereleaseVersion, out string oauth2PackagesVersion) { string[] lines = File.ReadAllLines(VersionFile); string versionLine = lines[0]; prereleaseVersion = lines.Length >= 2 ? lines[1] : null; oauth2PackagesVersion = lines.Length >= 3 ? lines[2] : null; if (!String.IsNullOrEmpty(prereleaseVersion)) { if (!prereleaseVersion.StartsWith("-")) { // SemVer requires that prerelease suffixes begin with a hyphen, so add one if it's missing. prereleaseVersion = "-" + prereleaseVersion; } this.VerifyValidPrereleaseVersion(prereleaseVersion); } typedVersion = new Version(versionLine); } private int CalculateJDate(DateTime date) { int yearLastDigit = date.Year - 2000; // can actually be two digits in or after 2010 DateTime firstOfYear = new DateTime(date.Year, 1, 1); int dayOfYear = (date - firstOfYear).Days + 1; int jdate = yearLastDigit * 1000 + dayOfYear; return jdate; } private void VerifyValidPrereleaseVersion(string prerelease) { if (prerelease[0] != '-') { throw new ArgumentOutOfRangeException("The prerelease string must begin with a hyphen."); } for (int i = 1; i < prerelease.Length; i++) { if (!char.IsLetterOrDigit(prerelease[i])) { throw new ArgumentOutOfRangeException("The prerelease string must be alphanumeric."); } } } } }