05 July 2006

TFS Team Build: Problems with calling a custom target before building each individual solution - Part II

Hi folks and welcome to Part II.

Before I start, I'd just like to say Happy Independence Day from the heart of New England in the USA!

Ok, with that out of the way, I’ll give you all an overview of where I’m at in my quest to inject a custom task into a TFS Team Build while standing on my head juggling 6 live Carp…er…strike that last part about juggling Carp (although I am standing on my head most of my workday). Actually it’s injecting a custom task into a Team Build just prior to compilation of each project. As both of my faithful readers will remember, I was having a bit of a go around with Microsoft PSS. They wanted me to override Team Build’s CoreCompile target to get this thing to work even when the Team Build devs felt that it was a mistake to do so. Well, I have to agree with the Team Build devs. My managers did also…they felt that the risk of mucking with CoreCompile was too great and sent me down a different path to get my stuff to work. It’s soooo nice when management and I agree on stuff.

So basically my answer to you on the “Can I do a custom task just prior to each project compilation” is NO, don’t do it. Don't override CoreCompile or anything else in Microsoft.TeamBuild.targets. Don't muck around with the Visual Basic or C-Sharp project level targets either. If you have a small number of projects or a lot of time, you can modify your individual project files to perform your custom task. I have neither. I say this to you primarily because I have been told that the CoreCompile target WILL be changing in v.2 of TFS.

So what the heck am I doing now to complete this never-ending task? Glad you asked! I’m performing a 2–pass compile. As you may (or may not, I don’t really care which) remember, the real need was to run code generation before compiling because we have projects later in the build cycle whose code generation performs reflection over earlier assemblies. So I really need to do the following:

For each project in solution
    Check out code generation-centric code files
    Code Gen (if needed)
    Compile code
    Check in code generation-centric code files
Next project
If Successful, label source code in repository

The good thing is that we already have this running MSBuild under CruiseControl.NET and NAnt hitting our VSS repository. So now you are scratching your head, that way you do in meetings when management starts talking, and say “But Steve, this doesn’t get you anything more. It doesn’t get you code coverage metrics or test statistics or work items references. It doesn’t even touch TFS!” Well you are correct grasshopper, but it does get me a starting point for the first pass of my new and improved (well it’s new) Team Build. So I start by taking this existing work and with slight modification, have it hit TFS. Once I have it running against the new repository, I can then fire this process at the beginning of the Team Build by EXECing (is that a word?) to NAnt (cutting out CruiseControl.NET). Once this first pass is completed successfully, we let the Team Build run as normal doing it’s whole “Lets compile everything”, “Lets label everything”, “Lets test everything” process.

So now you’re saying “But Steve, Isn’t this just a different type of kludge??” Why Yes, it is. But it is a maintainable kludge since we already have in-house devs that know how to work with NAnt and more importantly, NAnt can already do this! The key point here is that I get my code generation to work and ensure that it compiles in NAnt prior to letting the Team Build fire against it. So if pass 1 completes successfully then I know that the generated code that is in TFVC compiles and is ready for testing and such. When Team Build fires, I get logging, metrics, code coverage and all of the really great TFS reporting stuff. If the first pass fails, Team Build notifies me and logs that as well. Best of both worlds! Also, in the future (maybe v.2?) the Team Build may support what I need. When that happens, I can just remove the EXEC to NAnt and integrate the code generation task right into the JustBeforeWeActuallyCompileTheProject target (or whatever name Microsoft comes up with, although I truly believe that my name reflects the true nature of the target).

Ok great, so here’s what my build looks like now:

Begin TeamBuild
    Begin NAnt build
        For each project in solution
            Check out code generation-centric files
            Run code generation Compile project using MSBuild
            If compile succeeds then
                Check in all files checked out earlier
                Return success
            Else
                Undo check out on all files checked out earlier
                Return failure
            End If
        Next project
        If all projects succeeded then
            Label repository at file versions in workspace
            Return success to TeamBuild
        Else
            Return failure to TeamBuild
        End If
    End NAnt build

    Skip workspace create
    Skip clean
    Skip get

    If NAnt build completed successfully then
        Begin Solution Build
            Compile code
            Run Tests
            Run code coverage
            Report to Team Foundation Server
        End Solution Build
End TeamBuild

Ok, another really really long post. Glad you've kept with me. As Brain says to Pinky; "Are you pondering what I'm pondering?" You: "Uh...yeah, Steve, but won't the monkeys get tired?" Ummmm..no. Actually we are pondering if this actually works. As you all know, or will in a moment, I don't know a whole lot of things that work in Team Build, BUT I do know a whole lot of things that don't work...and this is one of them.

It seems that when you get to the Compile project using MSBuild step, the MSBuild process just hangs. This is strange because when I run the TeamBuild or the NAnt build separately, each works 100%. To diagnose the issue, I checked the Processes tab in Task Manager to see what was going on. In Task Manager, I saw MSBuild and NAnt both sitting there taking up no more than 1% CPU (and usually less), but nothing being written to the logs. I explicitly terminated the MSBuild task and the NAnt task ran to completion which included spinning up MSBuild processes for each project in the solution, but the TeamBuild returned a failure. Quite an interesting dilemma, No? Yes, it seems like the MSBuild process spawned by the TeamBuild doesn't like it when an EXEC'd process fires up an MSBuild process. I didn't dig too much further into the whys and wherefores of it, but I did notice that there wasn't a second MSBuild process in Task Manager, just the first one fired by TeamBuild. Could this mean that the second instance of MSBuild is running in the first instance's process space? Could be, I don't know, but if anyone out there figures this out, please let me know...I'm kinda curious.

I have now shown you a number of things that don't work. Let's look at one that does. To get beyond the hanging MSBuild problem we need a solution that will be maintainable, easy to implement and actually works. To get this thing to work, we need to reverse the flow of control in this scenario. Instead of having the TeamBuild start an MSBuild that fires up NAnt which spawns MSBuild tasks (ouch...my head hurts), we will have CruiseControl.NET start NAnt that will fire of MSBuilds for each project and then spin up a TeamBuild on the newly labeled code. Here's the flow in pseudocode:

CruiseControl.NET detects check-in to TFVC
    Begin NAnt build
        Create workspace
        Clean source code folders
        Get source code from TFS
        For each Project in Solution
            Check out code generation-centric files
            Run code generation
            Compile project using MSBuild
            If compile succeeds then
                Check in all files checked out earlier using well-know label
                Return success
            Else
                Undo check out on all files checked out earlier
                Return failure
            End If
        Next Project

        If NAnt build completed successfully then
            Label all source code in workspace
            Call TFS Server to request a TeamBuild (TFSBuild.exe)
                Begin Team Build
                    Skip workspace create
                    Skip clean Skip get
                    Begin Solution Build
                        Compile code
                        Run Tests
                        Run code coverage
                        Report to Team Foundation Server
                    End Solution Build
                End Team Build
            End Call to TFS Server
        End If
    End NAnt build

We utilize the VSTS plug-in for CruiseControl.NET to determine when a check-in has occurred to kick off the build. In my case, I also need to figure out a way to stop the check-in that occurs during the build from triggering another build in CruiseControl.NET. I will be looking into this in the near future and will add another blog entry with whatever I find out. I'm sure that the answer will revolve around checking-in files using a specific label and filtering out those check-ins based on that.

Phew...that was a long one. If you have any questions or would like more detail, please leave me a comment and I'll do my best to accommodate you.

1 comments:

Steven St Jean said...

Dammit! A few minutes too late. Well... I tried to get it up on the 4th. Just my luck, burned again.

Post a Comment