Skip to content

Instantly share code, notes, and snippets.

@janhebnes
Last active July 28, 2017 19:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janhebnes/9b7f76564b6c3c8a5cb0d9e8dc93f9f5 to your computer and use it in GitHub Desktop.
Save janhebnes/9b7f76564b6c3c8a5cb0d9e8dc93f9f5 to your computer and use it in GitHub Desktop.
MSBuild - Just include in project file. Detects on project folder level latest TFS or Git Revision using tf.exe history or git rev-list count HEAD for a revision number and git log -1 pretty=format:%h . for hash and replaces the Revision in AssemblyVersion and AssemblyFileVersion for AssemblyInfo.cs prior to building the project with CustomTasks…
<!--
***********************************************************************************************
Embed in csproj with import and the build events are hooked up automatically
<Import Project="..\..\..\Common.targets" Condition="Exists('..\..\..\Common.targets')" />
PreBuild - Handling revision on build so the source control revision number of the project folder is embeded in the last numeric of the projects AssemblyInfoVersion and AssemblyInfoFileVersion
PostBuild - Sets revision to 0 and reverts the file with a copy upon finishing for not introducing change on version control for the assemblyInfo.cs
In debug mode the revision detection is disabled, and notification is shown instead.
CustomTasks
# DetectSourceControlRevision
Using Shell command line on the ProjectDir detects the latest revision of the folder.
TeamFoundation is detected using tf history if command is not succesfull Git is attempted,
Git is using git rev-list - - count HEAD for a revision number and git log -1 - - pretty=format:%h . for a revision hash
<DetectSourceControlRevision Files="$(ProjectDir)">
<Output TaskParameter="RevisonNumber" PropertyName="Revison"/>
<Output TaskParameter="RevisionHash" PropertyName="CommitID" />
<Output TaskParameter="Tracert" PropertyName="TraceMessages" />
</DetectSourceControlRevision>
# UpdateAssemblyInfoVersionRevision
Replaces the last digit on assemblyInfo Version attributes with parameter SetRevision.
If revision hash is provided an AssemblyInformationVersion is appended to the AssemblyInfo allowing use of non numericals for storing the revision hash with pattern x.x.x.revision-revisionhash.
<UpdateAssemblyInfoVersionRevision Files="$(ProjectDir)Properties\AssemblyInfo.cs" SetRevision="0" SetRevisionHash="" />
***********************************************************************************************
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="DetectSourceControlRevision" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<RevisionNumber ParameterType="System.String" Output="True" />
<RevisionHash ParameterType="System.String" Output="True" />
<Tracert ParameterType="System.String" Output="True" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Diagnostics" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Tracert = string.Empty;
try
{
for (int i = 0; i < Files.Length; ++i)
{
Tracert += "\nFiles.Length:"+Files.Length;
var path = Files[i].GetMetadata("FullPath");
Tracert += "\npath:"+path;
if (!(Directory.Exists(path) || File.Exists(path))) continue;
Tracert += "\npathExists:true";
string VS140COMNTOOLS = Environment.GetEnvironmentVariable("VS140COMNTOOLS");
string pathToTool = VS140COMNTOOLS + @"..\IDE\";
string toolName = "tf.exe";
string commandline = " history /s:\"http://tfs2013-v3:8080/tfs\" /stopafter:1 /noprompt /recursive /version:W .\\ ";
if (!File.Exists(pathToTool+toolName))
{
// VS140 not found - trying vs2017 default location (vs2017 does not have a Environment Variable)
pathToTool = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\";
}
if (!File.Exists(pathToTool+toolName))
{
Log.LogMessage(MessageImportance.High, "Changeset not detected - Tf.exe could not be located");
RevisionNumber = "0";
return true;
}
using (System.Diagnostics.Process process = new System.Diagnostics.Process())
{
process.StartInfo.FileName = Path.GetFullPath(pathToTool+toolName);
process.StartInfo.Arguments = commandline;
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
Tracert += "\nCalling FileName" + process.StartInfo.FileName;
Tracert += "\nCalling Arguments " + process.StartInfo.Arguments;
Tracert += "\nCalling WorkingDirectory " + process.StartInfo.WorkingDirectory;
process.Start();
process.WaitForExit();
var result = process.StandardOutput.ReadToEnd();
// ParseResult fails detect GIT revision information if present
const string changeset = "Changeset";
if (string.IsNullOrEmpty(result) || !result.Contains(changeset))
{
// Detect potential Git revision hash using git rev-list --count HEAD
process.StartInfo.FileName = "git";
process.StartInfo.Arguments = "rev-list HEAD --count -- .";
process.Start();
process.WaitForExit();
var gitresult = process.StandardOutput.ReadToEnd();
if (!string.IsNullOrEmpty(gitresult))
{
if (!result.Contains("fatal"))
{
RevisionNumber = gitresult.Trim();
// Detect Git hash using git log -1 --pretty=format:%h .
process.StartInfo.FileName = "git";
process.StartInfo.Arguments = "log -1 --pretty=format:%h .";
process.Start();
process.WaitForExit();
gitresult = process.StandardOutput.ReadToEnd();
if (!string.IsNullOrEmpty(gitresult))
{
if (!result.Contains("fatal"))
{
RevisionHash = gitresult.Trim();
}
}
return true;
}
}
Tracert += "\nChangeset not detected - empty results for parsing";
Log.LogMessage(MessageImportance.High, "Changeset not detected - empty results for parsing");
RevisionNumber = "0";
return true;
}
var resultLines = result.Split(Convert.ToChar("\n"));
if (resultLines.Length != 4)
{
Tracert += "\nChangeset not detected - invalid results for parsing";
Log.LogMessage(MessageImportance.High, "Changeset not detected - invalid results for parsing");
RevisionNumber = "0";
return false;
}
var revisionLine = resultLines[2];
var indexOfFirstTab = revisionLine.IndexOf(" ", StringComparison.InvariantCulture);
var revision = revisionLine.Substring(0, indexOfFirstTab).Trim();
//Log.LogMessage(MessageImportance.High, "Changeset " + revision + " detected");
RevisionNumber = revision;
}
}
Tracert += "\nDone";
return true;
}
catch (Exception ex)
{
Log.LogMessage(MessageImportance.High, "Common.targets DetectSourceControlRevision threw exception: " + ex.ToString());
}
]]>
</Code>
</Task>
</UsingTask>
<!-- Example: <DetectSourceControlRevision Files="$(ProjectDir)"><Output TaskParameter="RevisionNumber" PropertyName="Revision"/></DetectSourceControlRevision> -->
<UsingTask TaskName="UpdateAssemblyInfoVersionRevision" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<SetRevision ParameterType="System.String" Required="true" />
<SetRevisionHash ParameterType="System.String" Required="false" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Text.RegularExpressions" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
var rx = new System.Text.RegularExpressions.Regex("Version\\(\"(\\d+)\\.(\\d+)(\\.(\\d+)\\.(\\d+)|\\.*)\"\\)");
bool changed = false;
for (int i = 0; i < Files.Length; ++i)
{
var path = Files[i].GetMetadata("FullPath");
if (!File.Exists(path)) continue;
var txt = File.ReadAllText(path);
string newVersion = string.Empty;
foreach (Match match in rx.Matches(txt))
{
int lastDot = match.Value.LastIndexOf(".", StringComparison.InvariantCulture);
newVersion = match.Value.Substring(0, lastDot) + "." + this.SetRevision + "\")";
if (match.Value != newVersion)
{
txt = txt.Replace(match.Value, newVersion);
changed = true;
}
}
// Used for storing the Git Commit ID reference in a visible location, only present if run on a git repo
// AssemblyInformationalVersion is injected if not present and if a newVersion was detected in the assemblyInfo file.
// AssemblyInformationalVersion can contain non numeric characters
if (changed
&& !txt.Contains("AssemblyInformationalVersion")
&& !string.IsNullOrEmpty(newVersion)
&& !string.IsNullOrEmpty(this.SetRevisionHash))
{
// nb. newVersion comes as Version("x.x.x.x")
txt = txt + "\r\n[assembly: AssemblyInformational"+ newVersion.Substring(0, newVersion.Length-2) + "-" + this.SetRevisionHash + "\")]";
}
if (changed)
{
File.WriteAllText(path, txt);
}
}
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
<!-- Example: <UpdateAssemblyInfoVersionRevision Files="$(ProjectDir)Properties\AssemblyInfo.cs" SetRevision="0" /> -->
<Target Name="PatchAssemblyRevision" BeforeTargets="BeforeBuild" Condition="'$(Configuration)' != 'Debug' ">
<DetectSourceControlRevision Files="$(ProjectDir)">
<Output TaskParameter="RevisionNumber" PropertyName="Revision" />
<Output TaskParameter="RevisionHash" PropertyName="CommitID" />
<Output TaskParameter="Tracert" PropertyName="TraceMessages" />
</DetectSourceControlRevision>
<Message Text="Detected latest Revision as $(Revision) $(CommitID) for $(ProjectDir)" Importance="High" Condition=" '$(Revision)' != '' AND '$(Revision)' != '0' "/>
<Message Text="Could not detect latest Revision for $(ProjectDir)" Importance="High" Condition=" '$(Revision)' == '' OR '$(Revision)' == '0' "/>
<Message Text="Trace: $(TraceMessages)" Importance="High" Condition=" '$(Revision)' == '' OR '$(Revision)' == '0' "/>
<!-- Store copy for later reverting the change, the revision should only be embedded in the file build not the source code -->
<Delete Files="$(ProjectDir)Properties\AssemblyInfo.cs.prebuild" Condition="Exists('$(ProjectDir)Properties\AssemblyInfo.cs.prebuild')"/>
<Copy SourceFiles="$(ProjectDir)Properties\AssemblyInfo.cs" DestinationFiles="$(ProjectDir)Properties\AssemblyInfo.cs.prebuild" Condition="Exists('$(ProjectDir)Properties\AssemblyInfo.cs')"/>
<UpdateAssemblyInfoVersionRevision Files="$(ProjectDir)Properties\AssemblyInfo.cs" SetRevision="$(Revision)" SetRevisionHash="$(CommitID)" Condition=" '$(Revision)' != '' AND '$(Revision)' != '0' "/>
<Message Text="Revision $(Revision) Set in $(ProjectDir)Properties\AssemblyInfo.cs" Importance="High" Condition=" '$(Revision)' != '' AND '$(Revision)' != '0' "/>
</Target>
<Target Name="RevertAssemblyRevision" AfterTargets="AfterBuild" Condition="'$(Configuration)' != 'Debug' ">
<UpdateAssemblyInfoVersionRevision Files="$(ProjectDir)Properties\AssemblyInfo.cs" SetRevision="0" SetRevisionHash="" Condition=" '$(Revision)' != '' AND '$(Revision)' != '0' "/>
<Copy SourceFiles="$(ProjectDir)Properties\AssemblyInfo.cs.prebuild" DestinationFiles="$(ProjectDir)Properties\AssemblyInfo.cs" Condition="Exists('$(ProjectDir)Properties\AssemblyInfo.cs.prebuild')"/>
<Delete Files="$(ProjectDir)Properties\AssemblyInfo.cs.prebuild" Condition="Exists('$(ProjectDir)Properties\AssemblyInfo.cs.prebuild')"/>
</Target>
<Target Name="NotifyAssemblyRevisionIsDisabled" AfterTargets="AfterBuild" Condition="'$(Configuration)' == 'Debug' ">
<Message Text="Stepped over latest Revision detection for $(ProjectDir) due to active Debug mode" Importance="High" />
</Target>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment