Doing builds and running unit tests after every check-in has become a standard practice in Agile and non-Agile development teams. Continuous Integration is the term that describes this process and helps ensure that that nothing has been broken since the last check-in. One area that is sometimes overlooked is the code generation. Continuous Generation, the re-generating of code / stored procedures for every check-in and build, should be considered for your TFS builds. I believe there is a lot of benefit to generating the code with every build. There are several benefits:
- Table schema and stored procedure parameters can change. How do you know that your generated objects are up to date and match the database?
- It keeps everyone honest so they don’t break the golden rule of never editing generated code. If the build is going to re-generate the code there’s no way to sneak the semicolon or curly bracket that you manually fix each time after generation.
- Sometimes we generate just the class we are working on and keep the rest checked in so it doesn’t overwrite the current object. If any changes are made to the template only this new class will have them. The other classes will be based on the older version.
- It gives more credibility to the generation process. There is sometimes a feeling of nervousness when generating all of the objects. Since generated code is never edited, you can generate the code as often as you wish.
CodeSmith Tools is the code generation tool that we use. It offers an easy way to accomplish this from your Team Foundation Builds. With version 3.2 and newer there is a custom build task included with the Professional edition. Here is the online help with some information about it.
There are a couple things to consider. The generated classes will be checked-in to source control. You will need to check them out before you call the task and then check them back in afterwards. Also, the user guide instructions do not work quite right in the link above. The guide explains how to use it within your visual studio project. However, the task needs to be called from Team Build. Here’s the basic steps and changes for your CI tfsbuild.proj file.
Step 1: Install CodeSmith Professional on your TFS build server. This will install the MSBuild task and Targets file. I contacted CodeSmith Tool’s sales department and this does require an additional license for the server.
Step 2: Import the CodeSmith Targets file. Add the following line just below the import element below the <!-- Do not edit this --> comment.
<Import Project="$(MSBuildExtensionsPath)\CodeSmith\CodeSmith.targets" />
Step 3: Add TF property in the PropertyGroup element specifying the tf.exe location to be used throughout the process.
<PropertyGroup>
<TF>"$(TeamBuildRefPath)\..\tf.exe"</TF>
</PropertyGroup>
Step 4: Override the AfterGet target to check out the generated file(s). This must be called before the CodeSmith task or it will return an access denied error. This example demonstrates one file but you can use a subfolder or naming prefix for the generated files and recursively check out all of the necessary files.
<Target Name="AfterGet">
<Exec WorkingDirectory="$(SolutionRoot)\Main\TestCodeSmithMSBuild\"
Command="$(TF) checkout Measure.cs" />
Step 5: Call the CodeSmith task to execute the CodeSmith project(s) in your solution. This will also be called in the AfterGet target. Currently the example in the documentation incorrectly shows the CodeSmith task using the ProjectFile attribute. As the usage description shows, the attribute is actually ProjectFiles.
<CodeSmith ProjectFiles="$(SolutionRoot)\Main\TestCodeSmithMSBuild\Test.csp" />
Step 6: Override the AfterCompile target to check in the updated generated files. Fortunately TFS will only check in the file if there is a change. This is good because most of the time re-generating the code should generate the same thing each time. However, tf.exe returns a code of 1 instead of 0 and results in a partial success of the build. Use the IgnoreExitCode=”true” to ignore this. You could additionally update this to create a work item for any other return code. Here’s a post with a good example of this.
<Target Name="AfterCompile" Condition="'$(IsDesktopBuild)'!='true'">
<Exec WorkingDirectory="$(SolutionRoot)\Main\TestCodeSmithMSBuild"
IgnoreExitCode="true"
Command="$(TF) checkin /comment:"***NO_CI***Auto-Generate" /noprompt /override:"Auto Generate" measure.cs "/>
</Target>
Step 7: Undo the check out if the build fails by overriding the BeforeOnBuildBreak.
<Target Name="BeforeOnBuildBreak" Condition="'$(IsDesktopBuild)'!='true'">
<Exec WorkingDirectory="$(SolutionRoot)\Main\TestCodeSmithMSBuild"
Command="$(TF) undo /noprompt measure.cs"/>
</Target>
That is it. Enjoy!
Mike