I’ve used to compile projects with NAnt. I’m using MSBuild currently. Main reason was to unify build scripts during projects building inside and outside of Visual Studio. NAnt was helping me to create dynamically AssemblyInfo and than to compile into project. Attributes such as AssemblyTitle or AssemblyCompany were managed from one place and project version was generated with SVN revision based on. AssemblyInfo file wasn’t included into the project that’s why Solution Explorer didn’t even know about its existence. There was no problem with building project and messages about missing AssemblyInfo file despite of absence of this file in SVN repository. Now it is time MSBuild do this same for me :).
Let’s start with example of file compiling which is absent in Solution Explorer. Just add new AssemblyInfo.cs and then make it "Exclude From Project". Now we need to add it dynamicaly to the list of compiled files. Let’s modify our actual project file - .csproj, find target named "BeforeBuild" and put there some lines …
1: <Target Name="BeforeBuild">
2: <CreateItem Include="AssemblyInfo.cs" Condition="Exists(’AssemblyInfo.cs’)">
3: <Output ItemName="Compile" TaskParameter="Include"/>
4: </CreateItem>
5: </Target>
As you probably noticed AssemblyInfo.cs has been compiled with our project although it is not visible in Solution Explorer.
It will be good if all our projects in solution will have generated such information in the same manner. Let’s do some refactoring of our project file and make external helper file, which I usually call "Common.proj". Make "Common.proj" looks like this …
1: <?xml version="1.0" encoding="utf-8"?>
2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
3:
4: <!– AssemblyInfo Properties –>
5: <PropertyGroup>
6: <AssemblyInfoFile>AssemblyInfo.cs</AssemblyInfoFile>
7: </PropertyGroup>
8:
9: <!– Add additional depends to Build target –>
10: <PropertyGroup>
11: <BuildDependsOn>
12: IncludeGeneratedAssemblyInfo;
13: $(BuildDependsOn)
14: </BuildDependsOn>
15: </PropertyGroup>
16:
17: <Target Name="IncludeGeneratedAssemblyInfo">
18: <CreateItem Include="$(AssemblyInfoFile)" Condition="Exists(’$(AssemblyInfoFile)’)">
19: <Output ItemName="Compile" TaskParameter="Include"/>
20: </CreateItem>
21: </Target>
22: </Project>
We used property "BuildDependsOn" in lines (10-15), which is used by "Build" target and contains list of depended targets. Now we don’t need to modify "BeforeBuild" target in .csproj file it will be enough to include "Common.proj" in .csproj file. You can read more about "BuildDependsOn" in - How To: Add Custom Process at Specific Points During Build (Method #2)
Let’s edit beginning of our project file .csproj like this …
1: <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
2: <PropertyGroup>
3: <RootPath Condition=" ‘$(RootPath)’ == ‘’ ">$(MSBuildProjectDirectory)..\..\..\..\</RootPath>
4: </PropertyGroup>
5: <PropertyGroup>
6: <Configuration Condition=" ‘$(Configuration)’ == ‘’ ">Debug</Configuration>
7: … cut …
"RootPath" property defined in line (3) is a helper property which points out on folder root of our solution tree where "Common.proj" and other helper files are located. As I mentioned earlier we don’t need to modify "BeforeBuild" so part of .cproj file will look like this …
1: … cut …
2: <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
3: <Import Project="$(RootPath)\Common.proj" />
4: <!– To modify your build process, add your task inside one of the targets below and uncomment it.
5: Other similar extension points exist, see Microsoft.Common.targets.
6: <Target Name="BeforeBuild">
7: </Target>
8: <Target Name="AfterBuild">
9: </Target>
10: –>
11: … cut …
"Common.proj" is just included in line (3). From this time only this file is needed to modify.
We will use target "AssemblyInfo" for generating assembly info file and target "SvnInfo" to reach for actual svn’s revision number. These tasks are included in MSBuild Community Tasks.
1: <?xml version="1.0" encoding="utf-8"?>
2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
3:
4: <!– AssemblyInfo Properties –>
5: <PropertyGroup>
6: <AssemblyInfoFile>AssemblyInfo.cs</AssemblyInfoFile>
7: <AssemblyTitle>Title of my library.</AssemblyTitle>
8: <AssemblyDescription>My Product made by Me.</AssemblyDescription>
9: <AssemblyCompany>My Company Ltd.</AssemblyCompany>
10: <AssemblyProduct>My Product</AssemblyProduct>
11: <AssemblyVersion>1.0.0</AssemblyVersion>
12: </PropertyGroup>
13:
14: <!– Add additional depends to Build target –>
15: <PropertyGroup>
16: <BuildDependsOn>
17: IncludeGeneratedAssemblyInfo;
18: $(BuildDependsOn)
19: </BuildDependsOn>
20: </PropertyGroup>
21:
22: <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
23:
24: <Target Name="IncludeGeneratedAssemblyInfo">
25:
26: <!– Get the revision number of the local working copy –>
27: <SvnInfo LocalPath="$(MSBuildProjectDirectory)">
28: <Output TaskParameter="Revision" PropertyName="SvnRevision"/>
29: </SvnInfo>
30:
31: <AssemblyInfo CodeLanguage="CS"
32: OutputFile="$(AssemblyInfoFile)"
33: AssemblyTitle="$(AssemblyTitle)"
34: AssemblyDescription="$(AssemblyDescription)"
35: AssemblyCompany="$(AssemblyCompany)"
36: AssemblyProduct="$(AssemblyProduct)"
37: AssemblyVersion="$(AssemblyVersion).$(SvnRevision)"
38: AssemblyFileVersion="$(AssemblyVersion).$(SvnRevision)" />
39:
40: <CreateItem Include="$(AssemblyInfoFile)" Condition="Exists(’$(AssemblyInfoFile)’)">
41: <Output ItemName="Compile" TaskParameter="Include"/>
42: </CreateItem>
43: </Target>
44: </Project>
.. and that’s the final version.