This post originally appeared on the 7digital developer blog on 8th June 2011. It has been moved here for preservation. You can also use a newer TeamCity feature called Snapshot Dependencies for a cleaner way of managing dependent builds.
We have recently switched to using TeamCity to manage the building and updating of our shared code at 7digital, which is great. The process is fast, completely automated and configurable, which is a vast improvement over our old build process – very manual, error prone and could take up to 3 hours of a developer’s time.
We have a large set of “domain” dlls which contain a lot of legacy code shared between several applications. When someone updates this code, we need to ensure that:
- All domain dlls are compiled against each other for build integrity
- The newest version of the domain set is available to all projects
- All consumers should update their references as soon as possible to catch any bugs.
Here is how we do it using TeamCity and a set of project conventions.
Solution & Folder Structure
Each solution has multiple projects, and a lib folder which contains third party and in-house domain dlls used by the projects in that solution. By convention, the lib folder sits in the top level folder. Projects create references to the dlls straight from this location.
Updating a dll in the lib means that all projects will use the new version immediately. This is really handy for upgrading all projects to a new version of a third party tool like NUnit, RhinoMocks or StructureMap, but it also works for our own in-house dlls. All we need is an automated way of updating the dll in the lib folders to the latest version whenever someone commits a change. Enter TeamCity!
We’ve placed the set of in-house domain projects in a linear build order. Each project in the list is configured to trigger the next in line when it successfully builds, using the TeamCity “Dependencies” tab. If somone makes a change to a domain project, TeamCity will pick up the commit, build the project and run its tests, and this will kick off the rest of the chain underneath. In the screenshot examples below, I’ve used a portion of our domain chain where SevenDigital.Domain.Catalogue is dependent on SevenDigital.Domain.Catalogue.MetaData.
We split each project into two (or more) builds in TeamCity, which are run in order if the previous build succeeds. 0) Dependency 1) Build and Unit Test 2+) Integration Tests (if they exist), code metrics, etc.
Build and Unit Test
The (1) Build and Unit Test is a normal build triggered by developer check in, which builds the solution and runs unit tests. On each successful run, it will export the assemblies from its lib folder and \bin\debug folder to artifacts, using TeamCity artifact paths. The assemblies are then accessible by other TeamCity builds, and used by the (0) Dependency Build.
The (0) Dependency Build is always triggered by a previous build in the chain. It is responsible for updating the project lib folder with the latest versions of the previous build’s dlls, which sounds a bit complicated, but is easily broken into steps. We use the build agent like an automated developer – it checks out the project source code to a local folder, pulls down the artifact dlls from the previous build to the local lib folder, and then does a command line commit to either git or svn depending on where that project is hosted.
- On the “Version Control Settings”, we always set the VCS checkout mode to “Automatically on agent”. This means the source code goes to the build agent machine rather than on the central server.
- On the “Dependencies” tab, we add an Artifact Dependency to the previous Build & Unit Test in the chain, taking all of the published dlls. The destination path is set to “lib”, meaning the agent takes care of downloading the dlls into the local lib folder, effectively overwriting them (or adding new ones in to the folder if they don’t already exist). From a version control point of view, the lib folder now looks like it has updated files that are ready for checkin.
- We use an msbuild or rake task that executes a command line commit from the root folder. The agent already has a link back to the main repository, because we checked the code out directly to the agent.
(svn|git) add . (svn|git) commit -m "Auto Commit from $(agent_name) for build no $(build_number)"
- The commit from the agent is just like a regular checkin. It triggers (1) Build & Unit Test, and the cycle continues down the chain.
Setting up the entire chain took a large amount of configuration, but it’s been worth it. The biggest gain has been removing the manual component of the build, which means we get faster feedback if something is broken, and people are able to make changes more confidently.