-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTFSBuildNumber.targets
382 lines (328 loc) · 18.6 KB
/
TFSBuildNumber.targets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- TFSBuildNumber.targets, Version 1.3 - August, 2014 -->
<!-- Updates build numbers in projects based on the TFS build number.
Supports:
- Extracting the major/minor versions from C# version attributes in an
existing file (defaults to Properties\AssemblyInfo.cs).
- Updating C# version attributes in the info file.
- Writing build number into a plain Version.txt file in the project root
(e.g. for copying to output as content along with web site files)
- Reset the AssemblyInfo.cs file after the build (so it isn't
constantly being updated in version control, including TFS 2012
local workspaces).
- If assembly info file needs to be Read Only (so that older TFS source
control doesn't pick it up as a change) then can unset/set the
read only flag on the file.
To use:
1. Include TFSBuildNumber.targets in your solution, probably in
a shared location.
2. Edit .csproj files to Import the TFSBuildNumber.targets project.
3. Add BeforeBuild target to the .csproj, with a dependency on
the GetMajorMinorFromCSharpAssemblyInfoFile task to get the major/minor
numbers, and then UpdateCSharpAssemblyInfoFile to generate the
build number (build and revision parts) and update the file.
You can also add a dependency on WriteProjectTextAssemblyVersionFile
to generate a text file.
If you want to use a file other than the default (Properties/AssemblyInfo.cs),
then set the <VersionInfoFile> property.
4. Add AfterBuild target with a dependency on ResetCSharpAssemblyInfoFile
to reset the changes (if you don't want to check in changes to AssemblyInfo.cs
after every local build).
5. Set the TFSBaseBuild property in the .csproj file (defaults to 2010).
By setting the base year you will have nicely increasing version
numbers.
You can update the base year whenever you change the major or
minor version number.
6. If your AssemblyInfo.cs file is usually read only, then you need to set
the additional property <TFSSetReadOnly>true</TFSSetReadOnly> to handle this.
NOTE: If you don't want to use GetMajorMinorFromCSharpAssemblyInfoFile,
an alternative is to set the TFSMajorBuildNumber and TFSMinorBuildNumber
properties in your .csproj file.
Example to insert in your .csproj file (change TFSBaseBuildYear as desired):
<PropertyGroup>
<TFSBaseBuildYear>2010</TFSBaseBuildYear>
</PropertyGroup>
<Import Project="TFSBuildNumber.targets"/>
<Target Name="BeforeBuild"
DependsOnTargets="GetMajorMinorFromCSharpAssemblyInfoFile;WriteProjectTextAssemblyVersionFile;UpdateCSharpAssemblyInfoFile">
</Target>
<Target Name="AfterBuild"
DependsOnTargets="ResetCSharpAssemblyInfoFile">
</Target>
Additional optional properties that can be used:
<PropertyGroup>
<VersionInfoFile>$(MSBuildProjectDirectory)\VersionInfo.cs</VersionInfoFile>
<TFSMajorBuildNumber>1</TFSMajorBuildNumber>
<TFSMinorBuildNumber>0</TFSMinorBuildNumber>
<TFSSetReadOnly>true</TFSSetReadOnly>
</PropertyGroup>
-->
<!-- Based on original work in Wintellect.TFSBuildNumber.Targets
by John Robbins ([email protected]).
From the original description:
"A set of pure MSBuild 4.0 tasks that generate build version information
for a TFS 2010 Team Build in order to have your file versions match
exactly the TFS build numbers. Everything is in this file, you don't
need to install any other DLLs, assemblies, or magic on your TFS build
server. MSBuild 4.0 really is quite wonderful!"
-->
<PropertyGroup>
<!-- Figure out where the TFS build tasks are. -->
<TeamBuildRefPath Condition="'$(TeamBuildRefPath)'==''">$(VS100COMNTOOLS)..\IDE\PrivateAssemblies\</TeamBuildRefPath>
<!-- Figure out where I'm being called from, TFS Build or a developer
machine. BuildUri and TeamFoundationServerUrl properties are the
ones that tell me I'm running under TFS Build.-->
<WintellectBuildType>DEVELOPERBUILD</WintellectBuildType>
<WintellectBuildType Condition="'$(BuildUri)'!='' and '$(TeamFoundationServerUrl)'!=''">TFSBUILD</WintellectBuildType>
</PropertyGroup>
<!-- Set up the dependency on the InitializeBuildProperties task only
if running under TFS Build. -->
<PropertyGroup Condition="'$(WintellectBuildType)'=='TFSBUILD'">
<DependOnGetBuildProperties>GetBuildProperties</DependOnGetBuildProperties>
</PropertyGroup>
<PropertyGroup>
<VersionInfoFile Condition="'$(VersionInfoFile)'==''">$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs</VersionInfoFile>
<TFSBaseBuildYear Condition="'$(TFSBaseBuildYear)'==''">2010</TFSBaseBuildYear>
</PropertyGroup>
<!-- Use the GetBuildProperties task -->
<UsingTask TaskName="Microsoft.TeamFoundation.Build.Tasks.GetBuildProperties"
AssemblyFile="$(TeamBuildRefPath)\Microsoft.TeamFoundation.Build.ProcessComponents.dll" />
<Target Name="GetBuildProperties">
<GetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)" >
<!-- Outputs are the initial values for the various properties of the build. -->
<Output TaskParameter="BuildDefinitionName" PropertyName="BuildDefinitionName" />
<Output TaskParameter="BuildDefinitionUri" PropertyName="BuildDefinitionUri" />
<Output TaskParameter="BuildNumber" PropertyName="BuildNumber" />
</GetBuildProperties>
<Message Text="Build definition '$(BuildDefinitionName)', number '$(BuildNumber)', uri '$(BuildDefinitionUri)'." />
</Target>
<!-- The TFSBuildFileVersion target builds the string suitable for using to
generate an acceptable AssemblyFileVersion attribute with the exact
build being done by TFS Build 2010.
This assumes a format of $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)
for the build number format. Tweak if you are using a different format.
For the code below, the $(BuildNumber) property from TFS Build 2010 will
look like: Dev Branch Daily Build_20091107.14
The important properties output of this task are those that start with
TFS and are shown below.
(Property) (Example Value)
TFSFullBuildVersionString 3.1.21107.14
TFSBuildNumber 21107
TFSCalculatedYear 2
TFSBuildYear 2009
TFSBuildMonth 11
TFSBuildDay 07
TFSBuildRevision 14
-->
<Target Name="TFSBuildFileVersion"
DependsOnTargets="$(DependOnGetBuildProperties)">
<!-- Do the error checking to ensure the appropriate items are defined.-->
<Error Condition="'$(TFSMajorBuildNumber)'==''"
Text="TFSMajorBuildNumber is not defined."/>
<Error Condition="'$(TFSMinorBuildNumber)'==''"
Text="TFSMinorBuildNumber is not defined."/>
<PropertyGroup>
<!-- The separator string between the $(BuildDefinition) and the date
revision.-->
<BuildDefSeparatorValue>_</BuildDefSeparatorValue>
<!-- The separator between the date and revision.-->
<DateVerSeparatorValue>.</DateVerSeparatorValue>
</PropertyGroup>
<!-- The calculations when run on a TFS Build Server.-->
<PropertyGroup Condition="'$(WintellectBuildType)'=='TFSBUILD'">
<!-- Get where the timestamp starts-->
<tmpStartPosition>$([MSBuild]::Add($(BuildDefinitionName.Length), $(BuildDefSeparatorValue.Length)))</tmpStartPosition>
<!-- Get the date and version portion. ex: 20091107.14-->
<tmpFullDateAndVersion>$(BuildNumber.Substring($(tmpStartPosition)))</tmpFullDateAndVersion>
<!-- Find the position where the date and version separator splits
the string. -->
<tmpDateVerSepPos>$(tmpFullDateAndVersion.IndexOf($(DateVerSeparatorValue)))</tmpDateVerSepPos>
<!-- Grab the date. ex: 20081107-->
<tmpFullBuildDate>$(tmpFullDateAndVersion.SubString(0,$(tmpDateVerSepPos)))</tmpFullBuildDate>
<!-- Bump past the separator. -->
<tmpVerStartPos>$([MSBuild]::Add($(tmpDateVerSepPos),1))</tmpVerStartPos>
<!-- Get the revision string. ex: 14-->
<TFSBuildRevision>$(tmpFullDateAndVersion.SubString($(tmpVerStartPos)))</TFSBuildRevision>
<!-- Get the pieces so if someone wants to customize, they have them.-->
<TFSBuildYear>$(tmpFullBuildDate.SubString(0,4))</TFSBuildYear>
<TFSBuildMonth>$(tmpFullBuildDate.SubString(4,2))</TFSBuildMonth>
<TFSBuildDay>$(tmpFullBuildDate.SubString(6,2))</TFSBuildDay>
</PropertyGroup>
<PropertyGroup Condition="'$(WintellectBuildType)'=='DEVELOPERBUILD'">
<TFSBuildRevision>0</TFSBuildRevision>
<TFSBuildYear>$([System.DateTime]::Now.Year.ToString("0000"))</TFSBuildYear>
<TFSBuildMonth>$([System.DateTime]::Now.Month.ToString("00"))</TFSBuildMonth>
<TFSBuildDay>$([System.DateTime]::Now.Day.ToString("00"))</TFSBuildDay>
</PropertyGroup>
<PropertyGroup>
<!-- This is the Excel calculation "=MOD(year-2001,6)"-->
<!-- That's what it looks like DevDiv is using for their calculations. -->
<TFSCalculatedYear>$([MSBuild]::Subtract($(TFSBuildYear),$(TFSBaseBuildYear)))</TFSCalculatedYear>
<tmpBuildNumber>$(TFSCalculatedYear)$(TFSBuildMonth)$(TFSBuildDay)</tmpBuildNumber>
<TFSBuildNumber>$(tmpBuildNumber.TrimStart('0'))</TFSBuildNumber>
<TFSFullBuildVersionString>$(TFSMajorBuildNumber).$(TFSMinorBuildNumber).$(TFSBuildNumber).$(TFSBuildRevision)</TFSFullBuildVersionString>
</PropertyGroup>
<Message Text="TFS Build Version $(TFSFullBuildVersionString) (build type $(WintellectBuildType))." />
<!-- Do some error checking as empty properties screw up everything.-->
<Error Condition="'$(TFSFullBuildVersionString)'==''"
Text="Error building the TFSFullBuildVersionString property"/>
<Error Condition="'$(TFSBuildNumber)'==''"
Text="Error building the TFSBuildNumber property"/>
<Error Condition="'$(TFSCalculatedYear)'==''"
Text="Error building the TFSCalculatedYear property"/>
<Error Condition="'$(TFSBuildDay)'==''"
Text="Error building the TFSBuildDay property"/>
<Error Condition="'$(TFSBuildMonth)'==''"
Text="Error building the TFSBuildMonth property"/>
<Error Condition="'$(TFSBuildYear)'==''"
Text="Error building the TFSBuildYear property"/>
<Error Condition="'$(TFSBuildRevision)'==''"
Text="Error building the TFSBuildRevision property"/>
</Target>
<!-- This extracts the major/minor version from an existing
C-Sharp AssemblyInfo.cs file.
This means that rather than specifying major/minor in the
.CSPROJ build file, all you need to do is add a before build
dependency to this target, plus the update target.
-->
<Target Name="GetMajorMinorFromCSharpAssemblyInfoFile">
<CreateProperty Value='$(VersionInfoFile)'>
<Output TaskParameter="Value" PropertyName="SourceAssemblyInfoFile" />
</CreateProperty>
<Message Text="Reading version number from file $(SourceAssemblyInfoFile)." />
<CreateProperty Value='$([System.IO.File]::ReadAllText("$(SourceAssemblyInfoFile)"))'>
<Output
TaskParameter="Value"
PropertyName="OriginalAssemblyInfo" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Match($(OriginalAssemblyInfo),"AssemblyFileVersion\s*\(\s*(.*?)\s*\)").Groups[1].Value)'>
<Output
TaskParameter="Value"
PropertyName="AssemblyFileVersion" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Match($(AssemblyFileVersion),"^.(\d+)\.(\d+)\..*$").Groups[1].Value)'>
<Output
TaskParameter="Value"
PropertyName="TFSMajorBuildNumber" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Match($(AssemblyFileVersion),"^.(\d+)\.(\d+)\..*$").Groups[2].Value)'>
<Output
TaskParameter="Value"
PropertyName="TFSMinorBuildNumber" />
</CreateProperty>
<Message Text="Found version $(TFSMajorBuildNumber).$(TFSMinorBuildNumber)." />
</Target>
<!-- This target updates an existing C-Sharp AssemblyInfo.cs file.
This means that rather than having to modify projects and
link a central SharedAssemblyInfo.cs file, you can simply
add a before build dependency to the update target and
the existing file will be updated.
Note that the file is also changed back to read-only after
updating, so that it is not picked up as a changed file.
-->
<Target Name="UpdateCSharpAssemblyInfoFile"
DependsOnTargets="TFSBuildFileVersion" >
<CreateProperty Value='$(VersionInfoFile)'>
<Output TaskParameter="Value" PropertyName="SourceAssemblyInfoFile" />
</CreateProperty>
<CreateProperty Value='$(SourceAssemblyInfoFile)'>
<Output TaskParameter="Value" PropertyName="DestinationAssemblyInfoFile" />
</CreateProperty>
<CreateProperty Value='"$(TFSFullBuildVersionString)"'>
<Output TaskParameter="Value" PropertyName="AssemblyFileVersion" />
</CreateProperty>
<CreateProperty Value='"$(TFSMajorBuildNumber).$(TFSMinorBuildNumber).0.0"'>
<Output TaskParameter="Value" PropertyName="AssemblyVersion" />
</CreateProperty>
<Exec Condition="Exists('$(DestinationAssemblyInfoFile)') and '$(TFSSetReadOnly)'=='true'" Command='attrib -R "$(DestinationAssemblyInfoFile)"' IgnoreExitCode='true' />
<Message Text="Updating file '$(DestinationAssemblyInfoFile)' with file version $(AssemblyFileVersion)." />
<CreateProperty Value='$([System.IO.File]::ReadAllText("$(SourceAssemblyInfoFile)"))'>
<Output
TaskParameter="Value"
PropertyName="OriginalAssemblyInfo" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Replace($(OriginalAssemblyInfo),"AssemblyFileVersion\s*\(.*?\)","AssemblyFileVersion($(AssemblyFileVersion))"))'>
<Output
TaskParameter="Value"
PropertyName="FileVersionUpdatedInfo" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Replace($(FileVersionUpdatedInfo),"AssemblyVersion\s*\(.*?\)","AssemblyVersion($(AssemblyVersion))"))'>
<Output
TaskParameter="Value"
PropertyName="FullUpdatedInfo" />
</CreateProperty>
<WriteLinesToFile
File="$(DestinationAssemblyInfoFile)"
Lines='$(FullUpdatedInfo)'
Overwrite="true" />
<Exec Condition="'$(TFSSetReadOnly)'=='true'" Command='attrib +R "$(DestinationAssemblyInfoFile)"' />
</Target>
<!-- This resets the build and release numbers in AssemblyInfo.cs.
With local file based version control (including local workspaces in
VS2012) changes to AssemblyInfo.cs will be picked up for check in.
If you don't want every build to trigger a change for these files,
then reset the build and release numbers in the AfterBuild step.
Note: You don't want to ignore these files completely, as you need
to include them as part of the project (and once added changes will
be detected).
-->
<Target Name="ResetCSharpAssemblyInfoFile"
DependsOnTargets="TFSBuildFileVersion" >
<CreateProperty Value='$(VersionInfoFile)'>
<Output TaskParameter="Value" PropertyName="SourceAssemblyInfoFile" />
</CreateProperty>
<CreateProperty Value='$(SourceAssemblyInfoFile)'>
<Output TaskParameter="Value" PropertyName="DestinationAssemblyInfoFile" />
</CreateProperty>
<CreateProperty Value='"$(TFSMajorBuildNumber).$(TFSMinorBuildNumber).0.0"'>
<Output TaskParameter="Value" PropertyName="AssemblyFileVersion" />
</CreateProperty>
<CreateProperty Value='"$(TFSMajorBuildNumber).$(TFSMinorBuildNumber).0.0"'>
<Output TaskParameter="Value" PropertyName="AssemblyVersion" />
</CreateProperty>
<Exec Condition="Exists('$(DestinationAssemblyInfoFile)') and '$(TFSSetReadOnly)'=='true'" Command='attrib -R "$(DestinationAssemblyInfoFile)"' IgnoreExitCode='true' />
<Message Text="Reseting file '$(DestinationAssemblyInfoFile)' to file version $(AssemblyFileVersion)." />
<CreateProperty Value='$([System.IO.File]::ReadAllText("$(SourceAssemblyInfoFile)"))'>
<Output
TaskParameter="Value"
PropertyName="OriginalAssemblyInfo" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Replace($(OriginalAssemblyInfo),"AssemblyFileVersion\s*\(.*?\)","AssemblyFileVersion($(AssemblyFileVersion))"))'>
<Output
TaskParameter="Value"
PropertyName="FileVersionUpdatedInfo" />
</CreateProperty>
<CreateProperty Value='$([System.Text.RegularExpressions.Regex]::Replace($(FileVersionUpdatedInfo),"AssemblyVersion\s*\(.*?\)","AssemblyVersion($(AssemblyVersion))"))'>
<Output
TaskParameter="Value"
PropertyName="FullUpdatedInfo" />
</CreateProperty>
<WriteLinesToFile
File="$(DestinationAssemblyInfoFile)"
Lines='$(FullUpdatedInfo)'
Overwrite="true" />
<Exec Condition="'$(TFSSetReadOnly)'=='true'" Command='attrib +R "$(DestinationAssemblyInfoFile)"' />
</Target>
<!-- Writes to (or updates) a text file in the current project.
This would usually be an existing file that can then be copied
to the output directory (as content), e.g. so you can tell the
deployed version of web site files. -->
<Target Name="WriteProjectTextAssemblyVersionFile"
DependsOnTargets="TFSBuildFileVersion">
<CreateProperty Value='$(MSBuildProjectDirectory)\Version.txt'>
<Output TaskParameter="Value" PropertyName="ProjectTextVersionFile" />
</CreateProperty>
<Exec Condition="Exists('$(ProjectTextVersionFile)') and '$(TFSSetReadOnly)'=='true'" Command='attrib -R "$(ProjectTextVersionFile)"' IgnoreExitCode='true' />
<Message Text="Updating file '$(ProjectTextVersionFile)' with file version $(TFSFullBuildVersionString)." />
<ItemGroup>
<TXTLines Include="$(TFSFullBuildVersionString)"/>
</ItemGroup>
<WriteLinesToFile Overwrite="true"
File="$(ProjectTextVersionFile)"
Lines="@(TXTLines)"/>
<Exec Condition="'$(TFSSetReadOnly)'=='true'" Command='attrib +R "$(ProjectTextVersionFile)"' />
</Target>
</Project>