Last week I decided it was time to start generating regular builds of the web application my team is developing. We're using Team Foundation Server as our source control system, so it was a natural choice to use Team Foundation Build (TFB) to generate the compiled application. Unfortunately for me, TFB is a version 1 Microsoft product which means, well, there are a few things that don't work quite right or as well as you might hope.
Installing TFB was easy, though I would have thought it would have been automatically installed with the Team Foundation Server (TFS) install. However, your build server might not be your TFS server, so this does make some kind of sense. Configuring a new Build Type was also easy, though this brings up a whole new issue. A Build Type consists of various parameters on the configuration of the compiled code you want. However, there is very little guidance I could find on the rationale for selecting various configuration options. As it turns out, for building a web application, you must be very precise with this configuration or nothing will work. The "Active solution configuration" isn't so important, but the "Active solution platform" must be "Mixed Platforms" or you won't get any satisfaction.
Before I continue, there are a few things you must know about the solution I was attempting to build. The solution contains about six projects, and the web site itself has several references to other assemblies. Both of these conditions weigh heavily on how successful you'll be using TFB to build the solution. First, the number or projects, or more precisely, the type of projects included in the solution. The current release of TFB can't deal with either Database Projects (regular ones, not the new stuff in Data Dude) or with Reporting Services Projects. With Database Projects, you'll only get a warning during the build, but with Reporting Services Projects you'll get an error and the build will be marked as "Failed." Now, using the Configuration Manager in Visual Studio, you can indicate which projects are supposed to get built when the solution is built (meaning you can exclude certain projects). This works in Visual Studio, but TFB ignores such settings and tries to build everything in the solution anyway. The only way to fix this is to remove the offending projects from your solution. Very irritating.
Now comes the really fun part. We use a Web Deployment Project (WDP) to compile the web application into a folder that can be simply "xcopy"'d to a deployment folder. On our local developer workstations this works great. On TFB, you're in for a world of hurt. First off, TFB has no idea what a WDP is, so you'll get all kinds of build errors when it sees this and tries to build it. You have to either install the WDP add-in on your build server, or at least copy the relevant files to the proper location on the build server. Once that's done, TFB can perform a build on WDPs, but this will generally fail until you take some extra steps.
First, your web application's external file assembly references have to be set up so that TFB can see them. The best way to do this is to add a "Solution Folder" to your existing solution, and place all assemblies that your web application references in here. Then make sure to re-reference these assemblies in your web project so that it knows where to find the new location. If you do this, then TFB will find all the referenced assemblies because they become part of the solution and are referenced by relative path.
Next (and this is the real tricky part), you have to tell all the other projects in your solution upon which the web site depends to copy their compiled assemblies into the web project's bin folder. In Visual Studio, you would do this simply by making a project reference from the web site to another project but the combination of TFB and WDP has no idea how to deal with this. When your WDP is compiled, it will complain that it can't find the appropriate assembly references (errors such as "Type 'MyClass' is not defined") and the build will fail. To get around this, you have to add a post-build event to all projects in the solution upon which the web site is dependent. In my solution, for example, I have two class libraries that the web site depends on - a Business Layer library and a Data Access Library. In both of these projects, I have to specify a post build event command that will copy the compiled assemblies to the bin folder of the web project.
xcopy /Y /S /F "$(TargetPath)" "$(SolutionDir)WebProjectRoot\bin"
(by the way, the quotation marks are very important if your paths have spaces in them, as I learned when xcopy gave me an "exited with code 4" message - very helpful)
Once you have done this and if the planets are aligned just so, your web solution will finally have a successful build in TFB. Of course, the next question is "what do I do once the thing finally builds?" For me, I wanted to be able to navigate to the build server in a web browser so I could see what the latest build looks like (we also use Automaton to perform continuous integration - ironically, Automaton was the easiest component of this entire mess to get set up properly). What I ended up doing was simply creating a virtual directory to the build location for the WDP on the build server. It's always in the same location, so on every build the directory is updated with the latest bits. It works, and now everyone is happy (okay, maybe only I'm happy because it took me four bloody days to get this far and it's nice to finally have a sense of accomplishment).
Here are some references I found helpful in between beating my head on my desk trying to get this all to work:
Web Deployment projects in a Team Build (Martijn Beenes)
Resolving file references in team build (Manish Agarwal)
Team Build, Web Applications, Lemon Juice and Paper Cuts (Rob Conery)
Also, thanks to Luis Fraile and Swaha Miller on MSDN Forums for helping me with this problem.