If you do a quick Google for pros of setting up continuous integration you'll come across a lot of articles about how good setting up continuous integration (CI) is compared to googling cons of setting up continuous integration which basically gives you the same posts of the the pros search so we won't dig too much into that now .
How many times have you checked in your code, a couple hours later you get a call from someone at the office saying the code doesn't compile on their machine because there is a file missing from source control that is added to the solution? I know before we started putting every project (no mater how big or small) in a CI build this happened often to teams I worked with. Part of the reason we didn't setup CI builds was because it was this scary thing that developers didn't need to know anything about, that was someone else's job right.
This post will show you just how easy setting up CI with Visual Studio Team Services (VSTS) and Team Foundation Server (TFS) really is.
Before getting started it's worth noting the sample project we are using here C# MVC web application.
Creating a new Build
Open up a TFS, browse to a Team Project and then click on the
- Build & Release hub group
- Builds hub
- New button
- Visual Studio template (normally the default)
- Next button
The most common things you'd change on the next part of the dialog is
- The repository you want to build
- Ticking the Continuous integration checkbox
- Click Create
- Click Save
- Enter a Name for this definition
- Click OK
You have now created a successfully continuous integration build. Click the Queue new build button, click OK and watch all this hard work pay off
Now that wasn't that hard was it? In a small while depending on the size of your project you should see your project has successfully built
Now although we have done what we wanted there is a couple extra things I like doing to try get more value out of the build system in order to help understand where we make mistakes and to try cover a larger surface area with the build
Adding a bit of extra value to your CI
The extra value we are going to add is
- Building multiple configurations
- Create a bug on failed builds
- Clean before building
- Trigger on all branch check ins
- Using our build number formats
- Label sources
Start off by editing the build definition
Let's get started
Building multiple configurations
Developers often use pragma's to do different things in debug and release builds so we want to make sure we are testing both configurations. We'll start by
- Going to the Variables tab
- For the BuildConfiguration variable enter debug,release
Now we just need to tell the build system to use this variable to build multiple times
- Click on the Options tab
- Check the Multi-configuration section
- In the Multipliers enter BuildConfiguration
- Check the Parallel checkbox
So what we've done now is told the build system that we want 2 builds to run each time this build is triggered, once for the debug configuration and once for release and we want to run the builds in Parallel to take less time. Let's try our changes out
- Click Save (and OK in the dialog that pops up)
- Click Queue new build
- Notice we have both configurations showing here now
- Click OK
You should see both configurations are running and that they hopefully will both pass
That's all to get both configurations to run
Create a bug on failed builds
I like creating bugs when builds break but not just so there is bugs, I like adding tags to see which branches we are breaking the most. You would generally ask different questions when you see different branches show up, for example you never want to see things breaking for the master branch because code should be working 100% by the time it's merged into master.
Again we are going to edit the build definition and head over to the Options tab like with the previous section. This time around
- Check the Create Work Item on Failure checkbox
- Change the Type to Bug
- Click Add field
- For the first field, we'll add "System.Tags" with a value of "Build failed,$(Build.SourceBranch)"
- Add another field, this time around we'll add "System.Title" with a value of "Blog Demo Build $(Build.BuildNumber) failed"
What this is going to do is when the build fails create a new Bug, assign it to the person who manually kicked off the build or whoever checked in the code to make the build fail setting the title and tags for us.
Click Save as we did earlier and lets test the change. Because this is a CI Build lets find the code we are building and go make some changes to that code .
So in a Todo controller that I have, I decide that when we running in DEBUG configuration I don't want to place the code in a Try catch so that we can see the exceptions that occur instead. Notice how this method expects a return string which we forgot to put in
Being a 'bad developer' I'll just go and check this in without testing it.
Now we should get an email from TFS telling us we have a new bug assigned to us, opening that up we can see
- The title is set
- Tags are set showing Build failed as well as the branch we broke
- A link has been added to the failing build
If we open up the build we can see that the release build went through 100% and only the debug build broke. If we weren't building all our configurations we would not have noticed this
Job Done . I'm also going to fix that bug for the rest of the post.
Clean before building
This step you could argue has less value although I have had it with a couple of builds where artifacts left behind from a previous build cause us to see strange things, especially with a post coming up soon where I'll highlight this .
Again let's edit the build definition, this time we are going to
- Go to the Repository tab
- Change the Clean option to true
- Clean options to All build directories
Click Save to keep these changes. You do have to realize that you are sacrificing build time/speed for accuracy here, on bigger builds you may want to be more specific about what you clean up.
Trigger on all branch check ins
Earlier we added an option to tag which branch breaks our builds and currently we are only triggering our build off the master branch which is slightly not helpful. Let's do the following now
- Go to the Triggers tab, Under the Branch filters section
- Make sure we have a Include filter
- for all (*) branches, you can click on the refs/heads/master and just type * and press enter to get this
- Add an Exclude filter
- Enter users/* as that filter
Click Save. What we have done now is say that we want to trigger off any branch that a check-in takes place for although if there is a users branch we'll ignore those because we understand that guys do commit builds into branches tagged with their own name that could have bugs as that prototype things
Note that when you manually queue a build you can still select to queue for the user branch
It will build as normal (including creating bugs when the build breaks).
We all done. Builds will now trigger for any branch except those under a users path.
Using our build number formats
Another thing I really like doing, especially in this days fast paces world of releasing multiple times a day is use the build number formats in TFS to extend the scope of traceability into the compiled assemblies. Edit our build definition and
- Go to the General tab
- Change the Build number format to "$(date:yyyy.MM.dd)$(rev:.r)", basically just adding a period (.) between the year, month and day part of should already exist.
Click Save. This alone doesn't do much for us but gives us a build number that can be used for a assembly version number.
For this next part you'll need to install a extension from the Visual Studio Marketplace which I use to Update AssemblyInfo
After that is installed go back to your build definition and edit it again. Add a new task Update AssemblyInfo
and place it as the first step in your build
Under the properties you can choose what to fill in but I generally enter
- A very verbose description, here it's set to "Reason:$(Build.Reason); Branch: $(Build.SourceBranch); Configuration: $(BuildConfiguration); BuildPlatform: $(BuildPlatform); Build Number: $(Build.BuildNumber); Commit Id: $(Build.SourceVersion)"
- Note that Build.Reason is currently only supported by VSTS but will hopefully make it down to on-premises in the next version. You can see what variables are available on the docs page for variables.
Now if you kick off a build you'll see this information get stamped into your binary.
I then also like having this exposed from the apps themselves something like below
code in GitHub Gist.
This code relies on you mapping MVC attribute routes
If we have to build and deploy this code we should see all the info as we added it to the description
running this from a our local build would show Dev Build?
In my example here the deploy was just pulling the binary out of TFS of build and dropping it onto my dev machine to get the compiled info but as you can see we can trace this back to a specific branch, commit and build in TFS now. After we get the Build.Reason in TFS we will know some extra info like who kicked off the build (below screenshot from the build variables docs)
When we get this extra info this becomes that little bit more powerful
The last thing I usually do is label sources. For this edit the build definition and
- Go to the Repository tab
- Change the Label sources to On successful build
- Enter the label format as $(Build.BuildNumber)
This will now generate a lot of labels as you could expect so I normally adjust the Retention policy to remove this.
- Go to the Retention tab
- Check the Delete source label
- and change the branch filter to Include *
This works quite well if you are using Release Manager in TFS/VSTS as well as the policy on the releases is usually set to keep the builds around that are being used so you will always have the tags if needed for any builds actually used in your release process.
Click Save and run your build, after it's successfully run go to the code tab and you should see your new tag added
Tagging can be really useful for a couple of reasons like debugging production issues as you can check the code as of that exact build.
Setting up a CI build is really simple and with a little extra effort you can get a lot more benefit out of it then just the basic "yes my code is compiling" .