Skip to content

Commit 5f0a131

Browse files
committed
Support GitVersioning in .vcxprojs
Generate a .rc file and include it into the build pipeline for native code in a fashion similar to the `GenerateAssemblyVersionInfo` task. Fixes #94
1 parent d829311 commit 5f0a131

File tree

2 files changed

+347
-1
lines changed

2 files changed

+347
-1
lines changed

src/Nerdbank.GitVersioning.NuGet/build/Nerdbank.GitVersioning.targets

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
33
<PropertyGroup>
44
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5-
<VersionSourceFile>$(IntermediateOutputPath)\$(AssemblyName).Version$(DefaultLanguageSourceExtension)</VersionSourceFile>
5+
6+
<PrepareForBuildDependsOn>
7+
GenerateNativeVersionInfo;
8+
$(PrepareForBuildDependsOn);
9+
</PrepareForBuildDependsOn>
610

711
<PrepareResourcesDependsOn>
812
GenerateAssemblyVersionInfo;
@@ -39,6 +43,7 @@
3943
</PropertyGroup>
4044

4145
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.AssemblyVersionInfo"/>
46+
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.NativeVersionInfo"/>
4247
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.SetCloudBuildVariables"/>
4348
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.GetBuildVersion"/>
4449
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.CompareFiles"/>
@@ -121,6 +126,7 @@
121126

122127
<Target Name="GenerateAssemblyVersionInfo" DependsOnTargets="GetBuildVersion" Condition=" '$(GenerateAssemblyVersionInfo)' != 'false' ">
123128
<PropertyGroup>
129+
<VersionSourceFile>$(IntermediateOutputPath)\$(AssemblyName).Version$(DefaultLanguageSourceExtension)</VersionSourceFile>
124130
<NewVersionSourceFile>$(VersionSourceFile).new</NewVersionSourceFile>
125131
<UltimateResourceFallbackLocation Condition=" '$(DevDivProjectSubType)' != 'portable' ">UltimateResourceFallbackLocation.MainAssembly</UltimateResourceFallbackLocation>
126132
</PropertyGroup>
@@ -152,6 +158,39 @@
152158
</ItemGroup>
153159
</Target>
154160

161+
<Target Name="GenerateNativeVersionInfo" DependsOnTargets="GetBuildVersion" Condition=" '$(Language)'=='C++' and '$(GenerateAssemblyVersionInfo)' != 'false' ">
162+
<PropertyGroup>
163+
<VersionSourceFile>$(IntermediateOutputPath)\$(AssemblyName).Version.rc</VersionSourceFile>
164+
<NewVersionSourceFile>$(VersionSourceFile).new</NewVersionSourceFile>
165+
</PropertyGroup>
166+
<MakeDir Directories="$(IntermediatePath)"/>
167+
<Nerdbank.GitVersioning.Tasks.NativeVersionInfo
168+
OutputFile="$(NewVersionSourceFile)"
169+
CodeLanguage="$(Language)"
170+
ConfigurationType="$(ConfigurationType)"
171+
AssemblyName="$(AssemblyName)"
172+
AssemblyVersion="$(AssemblyVersion)"
173+
AssemblyFileVersion="$(AssemblyFileVersion)"
174+
AssemblyInformationalVersion="$(AssemblyInformationalVersion)"
175+
AssemblyTitle="$(AssemblyTitle)"
176+
AssemblyProduct="$(AssemblyProduct)"
177+
AssemblyCopyright="$(AssemblyCopyright)"
178+
AssemblyCompany="$(AssemblyCompany)"
179+
AssemblyLanguage="$(AssemblyLanguage)"
180+
AssemblyCodepage="$(AssemblyCodepage)"
181+
TargetFileName="$(TargetFileName)"
182+
/>
183+
<!-- Avoid applying the newly generated Version.rc file to the build
184+
unless it has changed in order to allow for incremental building. -->
185+
<Nerdbank.GitVersioning.Tasks.CompareFiles OriginalItems="$(VersionSourceFile)" NewItems="$(NewVersionSourceFile)">
186+
<Output TaskParameter="AreChanged" PropertyName="_NativeVersionInfoChanged" />
187+
</Nerdbank.GitVersioning.Tasks.CompareFiles>
188+
<Copy Condition=" '$(_NativeVersionInfoChanged)' == 'true' " SourceFiles="$(NewVersionSourceFile)" DestinationFiles="$(VersionSourceFile)" />
189+
<ItemGroup>
190+
<ResourceCompile Include="$(VersionSourceFile)" />
191+
</ItemGroup>
192+
</Target>
193+
155194
<!-- Support for pattern found in users of Tvl.NuGet.BuildTasks. -->
156195
<Target Name="SupplyNuGetManifestVersion"
157196
DependsOnTargets="GetBuildVersion"
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
namespace Nerdbank.GitVersioning.Tasks
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using System.IO;
7+
using System.Text;
8+
using Microsoft.Build.Framework;
9+
using Microsoft.Build.Utilities;
10+
11+
public class NativeVersionInfo : Task
12+
{
13+
private const int VFT_APP = 0x1;
14+
private const int VFT_DLL = 0x2;
15+
16+
private const string FileHeaderComment = @"------------------------------------------------------------------------------
17+
<auto-generated>
18+
This code was generated by a tool.
19+
Runtime Version:4.0.30319.42000
20+
21+
Changes to this file may cause incorrect behavior and will be lost if
22+
the code is regenerated.
23+
</auto-generated>
24+
------------------------------------------------------------------------------
25+
";
26+
27+
private const string VersionInfoContent = @"#ifdef RC_INVOKED
28+
29+
#include <winres.h>
30+
31+
VS_VERSION_INFO VERSIONINFO
32+
FILEVERSION NBGV_FILE_MAJOR_VERSION,NBGV_FILE_MINOR_VERSION,NBGV_FILE_BUILD_VERSION,NBGV_FILE_REVISION_VERSION
33+
PRODUCTVERSION NBGV_PRODUCT_MAJOR_VERSION,NBGV_PRODUCT_MINOR_VERSION,NBGV_PRODUCT_BUILD_VERSION,NBGV_PRODUCT_REVISION_VERSION
34+
FILEFLAGSMASK 0x3FL
35+
#ifdef _DEBUG
36+
FILEFLAGS 0x1L
37+
#else
38+
FILEFLAGS 0x0L
39+
#endif
40+
FILEOS 0x4L
41+
FILETYPE NBGV_FILE_TYPE
42+
FILESUBTYPE 0x0L
43+
BEGIN
44+
BLOCK ""StringFileInfo""
45+
BEGIN
46+
BLOCK NBGV_VERSION_BLOCK
47+
BEGIN
48+
VALUE ""CompanyName"", NGBV_COMPANY
49+
VALUE ""FileDescription"", NGBV_TITLE
50+
VALUE ""FileVersion"", NBGV_FILE_VERSION
51+
VALUE ""InternalName"", NGBV_INTERNAL_NAME
52+
VALUE ""OriginalFilename"", NGBV_FILE_NAME
53+
VALUE ""ProductName"", NGBV_PRODUCT
54+
VALUE ""ProductVersion"", NBGV_INFORMATIONAL_VERSION
55+
VALUE ""LegalCopyright"", NBGV_COPYRIGHT
56+
END
57+
END
58+
59+
BLOCK ""VarFileInfo""
60+
BEGIN
61+
VALUE ""Translation"", NBGV_LCID, NBGV_CODEPAGE
62+
END
63+
END
64+
65+
#endif";
66+
67+
private CodeGenerator generator;
68+
69+
[Required]
70+
public string OutputFile { get; set; }
71+
72+
[Required]
73+
public string CodeLanguage { get; set; }
74+
75+
[Required]
76+
public string ConfigurationType { get; set; }
77+
78+
public string AssemblyName { get; set; }
79+
80+
public string AssemblyVersion { get; set; }
81+
82+
public string AssemblyFileVersion { get; set; }
83+
84+
public string AssemblyInformationalVersion { get; set; }
85+
86+
public string AssemblyTitle { get; set; }
87+
88+
public string AssemblyProduct { get; set; }
89+
90+
public string AssemblyCopyright { get; set; }
91+
92+
public string AssemblyCompany { get; set; }
93+
94+
public string AssemblyLanguage { get; set; }
95+
96+
public string AssemblyCodepage { get; set; }
97+
98+
public string TargetFileName { get; set; }
99+
100+
public override bool Execute()
101+
{
102+
this.generator = this.CreateGenerator();
103+
if (this.generator != null)
104+
{
105+
this.generator.StartFile();
106+
107+
this.generator.AddComment(FileHeaderComment);
108+
this.generator.AddBlankLine();
109+
110+
this.CreateDefines();
111+
this.generator.AddBlankLine();
112+
113+
this.CreateVersionInfo();
114+
115+
this.generator.EndFile();
116+
117+
Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile));
118+
File.WriteAllText(this.OutputFile, this.generator.GetCode());
119+
}
120+
121+
return !this.Log.HasLoggedErrors;
122+
}
123+
124+
private void CreateDefines()
125+
{
126+
var fileType = 0;
127+
128+
switch (this.ConfigurationType.ToUpperInvariant())
129+
{
130+
case "APPLICATION":
131+
fileType = VFT_APP;
132+
break;
133+
134+
case "DYNAMICLIBRARY":
135+
fileType = VFT_DLL;
136+
break;
137+
138+
default:
139+
this.Log.LogError("Unsupported ConfigurationType '{0}'. Only 'Application' and 'DynamicLibrary' are supported at this time.", this.ConfigurationType);
140+
return;
141+
}
142+
143+
if (!Version.TryParse(this.AssemblyFileVersion, out var fileVersion))
144+
{
145+
this.Log.LogError("Cannot process AssemblyFileVersion '{0}' into a valid four part version.", this.AssemblyFileVersion);
146+
return;
147+
}
148+
149+
if (!Version.TryParse(this.AssemblyVersion, out var productVersion))
150+
{
151+
productVersion = fileVersion;
152+
}
153+
154+
var lcid = 0;
155+
156+
if (!string.IsNullOrWhiteSpace(this.AssemblyLanguage))
157+
{
158+
if (!int.TryParse(this.AssemblyLanguage, out lcid))
159+
{
160+
#if NET45
161+
try
162+
{
163+
var cultureInfo = new CultureInfo(this.AssemblyLanguage);
164+
165+
lcid = cultureInfo.LCID;
166+
}
167+
catch
168+
{
169+
this.Log.LogError("Unknown AssemblyLanguage '{0}'. Cannot determine LCID for that culture.", this.AssemblyLanguage);
170+
return;
171+
}
172+
#else
173+
this.Log.LogError("Unknown AssemblyLanguage '{0}'. Must specify the language as an LCID.", this.AssemblyLanguage);
174+
#endif
175+
}
176+
}
177+
178+
if (!int.TryParse(this.AssemblyCodepage, out var codepage))
179+
{
180+
codepage = 0;
181+
}
182+
183+
var numericFields = new Dictionary<string, int>
184+
{
185+
{ "NBGV_FILE_MAJOR_VERSION", fileVersion.Major },
186+
{ "NBGV_FILE_MINOR_VERSION", fileVersion.Minor },
187+
{ "NBGV_FILE_BUILD_VERSION", fileVersion.Build },
188+
{ "NBGV_FILE_REVISION_VERSION", fileVersion.Revision },
189+
{ "NBGV_PRODUCT_MAJOR_VERSION", productVersion.Major },
190+
{ "NBGV_PRODUCT_MINOR_VERSION", productVersion.Minor },
191+
{ "NBGV_PRODUCT_BUILD_VERSION", productVersion.Build },
192+
{ "NBGV_PRODUCT_REVISION_VERSION", productVersion.Revision },
193+
{ "NBGV_FILE_TYPE", fileType },
194+
{ "NBGV_LCID", lcid },
195+
{ "NBGV_CODEPAGE", codepage },
196+
};
197+
198+
var stringFields = new Dictionary<string, string>
199+
{
200+
{ "NBGV_PRODUCT_VERSION", productVersion.ToString() },
201+
{ "NBGV_FILE_VERSION", fileVersion.ToString() },
202+
{ "NBGV_INFORMATIONAL_VERSION", DefaultIfEmpty(this.AssemblyInformationalVersion, productVersion.ToString()) },
203+
{ "NGBV_FILE_NAME", this.TargetFileName },
204+
{ "NGBV_INTERNAL_NAME", Path.GetFileNameWithoutExtension(this.TargetFileName) },
205+
{ "NGBV_TITLE", DefaultIfEmpty(this.AssemblyTitle, this.AssemblyName) },
206+
{ "NGBV_PRODUCT", DefaultIfEmpty(this.AssemblyProduct, this.AssemblyName) },
207+
{ "NBGV_COPYRIGHT", DefaultIfEmpty(this.AssemblyCopyright, $"Copyright (c) {DateTime.Now.Year}. All rights reserved.") },
208+
{ "NGBV_COMPANY", DefaultIfEmpty(this.AssemblyCompany, this.AssemblyName) },
209+
{ "NBGV_VERSION_BLOCK", (lcid << 16 | codepage).ToString("X8") },
210+
};
211+
212+
foreach (var pair in numericFields)
213+
{
214+
this.generator.AddDefine(pair.Key, pair.Value);
215+
}
216+
217+
foreach (var pair in stringFields)
218+
{
219+
if (!string.IsNullOrWhiteSpace(pair.Value))
220+
{
221+
this.generator.AddDefine(pair.Key, pair.Value);
222+
}
223+
}
224+
}
225+
226+
private void CreateVersionInfo()
227+
{
228+
this.generator.AddContent(VersionInfoContent);
229+
}
230+
231+
private CodeGenerator CreateGenerator()
232+
{
233+
switch (this.CodeLanguage.ToLowerInvariant())
234+
{
235+
case "c++":
236+
return new CodeGenerator();
237+
default:
238+
this.Log.LogError("Code provider not available for language: {0}. No version info will be embedded into assembly.", this.CodeLanguage);
239+
return null;
240+
}
241+
}
242+
243+
private class CodeGenerator
244+
{
245+
protected readonly StringBuilder codeBuilder;
246+
247+
internal CodeGenerator()
248+
{
249+
this.codeBuilder = new StringBuilder();
250+
}
251+
252+
internal void AddComment(string comment)
253+
{
254+
this.AddCodeComment(comment, "//");
255+
}
256+
257+
internal void StartFile()
258+
{
259+
this.codeBuilder.AppendLine("#pragma once");
260+
}
261+
262+
internal void AddContent(string content)
263+
{
264+
this.codeBuilder.AppendLine(content);
265+
}
266+
267+
internal void AddDefine(string name, int value)
268+
{
269+
this.codeBuilder.AppendLine($"#define {name} {value}");
270+
}
271+
272+
internal void AddDefine(string name, string value)
273+
{
274+
var escapedValue = "\"" + value.Replace("\\", "\\\\") + "\"";
275+
276+
this.codeBuilder.AppendLine($"#define {name} {escapedValue}");
277+
}
278+
279+
internal void EndFile()
280+
{
281+
}
282+
283+
internal string GetCode() => this.codeBuilder.ToString();
284+
285+
internal void AddBlankLine()
286+
{
287+
this.codeBuilder.AppendLine();
288+
}
289+
290+
protected void AddCodeComment(string comment, string token)
291+
{
292+
var sr = new StringReader(comment);
293+
string line;
294+
while ((line = sr.ReadLine()) != null)
295+
{
296+
this.codeBuilder.Append(token);
297+
this.codeBuilder.AppendLine(line);
298+
}
299+
}
300+
}
301+
302+
private static string DefaultIfEmpty(string value, string defaultValue)
303+
{
304+
return string.IsNullOrWhiteSpace(value) ? defaultValue : value;
305+
}
306+
}
307+
}

0 commit comments

Comments
 (0)