Over the course of the last couple months I have been working diligently to migrate the application I inherited from the wild wild west of unmanaged corner cutting contractor land to the happy kingdom of semi-intelligent source control.

I have successfully migrated what we have come to know as "what we think production is built with" source in to Team Foundation Server source control, cleaning up along the way. Why exactly this particular batch of contractors felt it was necessary to include references to both the 2.0 and 3.0 Enterprise Application Blocks, I may never know. I suspect they felt it was 2x more "Enterprise-y".

Next up on my path to Agile Nirvana, I whipped up a set of build scripts that produce a consistent build every time. Imagine that! My server support people are so happy with me.

The system I am working on is somewhat interesting. The application went live last summer and was officially handed over to a group of maintenance developers. A new project was immediately started to add new functionality while the maintenance guys fixed up bugs. I currently work with the New Development group.

The maintenance guys decided they wanted in on all this awesome TFS goodness. So I spun them off a branch and let them go on their merry way. That was until they decided they were ready to start rolling forward into production.

That is when I realized that all of my build scripts no longer worked. I would fire up a build using Team Builds in Team Explorer, it would do it's thing pulling down source code and firing off MSBuild to compile and then throw errors.

I checked on the build server in the location where source is downloaded and Team Build was no longer pulling down the source for my Main branch. The only thing be retrieved from Source Control was my Branches directory. Obviously the dirty maintenance branch hosed by lovely build process.

I checked my build definitions and mapping files. My source tree looks something like this:

$/PROJECT
..Branches
    ..2.0.1.0-APPLICATION-branch
..Main
   ..Framework
   ..APPLICATION
   ..Samples
   ..Utilites
..TeamBuildTypes
The 2.0.1.0 node is a branch of $/PROJECT/Main/APPLICATION. My workspace mapping pretty much looks like so:

<InternalMapping ServerItem="$/APPLICATION" LocalItem="flibbityjibits" Type="Map" />

I have a few cloaked paths as well all related to $/PROJECT/Main/Something. When I execute my Team Builds Branches and TeamBuildTypes are retrieved from source control and Main is ignored.

My buildlog shows that CoreGet is retrieving the latest:

Get Version="T" Recursive=True Force=True Workspace="NinjaWarrior"

There was something I was missing here. I shot off an email to the Seattle ALT.NET mailing list, more in a effort to articulate the issue well than expecting them to solve my issues or anything. Kinda just getting it all out as I understand the issue for the eventual call to tech support.

I went home that night utterly devastated that my painstakingly lovingly crafted system had failed so spectacularly.

At home I was playing some Lego Indy with the wife on the Xbox and letting the subconscious work on it. And finally the light bulb went off.

I have other branches in the Branches folder that caused no problem when they were created. I thought to myself, "Self, it's not the branch that did it.. It must be something that you did at the same time you created the branch."

My assumption about the branch breaking my build came directly from my Jump To Conclusions Mat.
I created this branch for an external group who is adding functionality. In an effort to keep my main pristine, I locked it down so that only members of the group "Tech Leads" could check in to Main.

This had to be the cause.. when I realized it last night I almost went back in to work... but come on its Lego Indy!

So I get to work this morning and checked the membership of "Tech Members" and surprise, surprise... TFSService is a member... The build service uses this account to access TFS...

So I quickly added it to the leads group and verified that my builds worked again and went to repair the dent I put in the wall from beating my head against it.

So Lessons Learned here:
1. Respect the build user, he can make you a very sad panda.
2. I've automated the build, its time to get a nightly build going so these changes are fresh in my mind when stuff breaks.
3. While I am at it might as well look into setting up CI to the Dev environment with the nightly.
4. Indy really can accomplish anything.

So in the end, I created my own problem. Thanks to all the ALT.NET mailing list readers for silently laughing at me while I verbalized (textualized?) the issue on the mailing list and worked through it.

I'll be here all week.


 
Categories: Development | MSBuild | Random

April 25, 2008
@ 12:35 PM

Based on my last post, I have decided my course of action should be to attempt to override the _ResolveReferences target supplied to me by Web Deployment targets build file.

A quick Google search lead me to Jomo Fisher's blog post titled "Hack the Build: Target Overriding Step-by-Step". A quick read through and I was ready to proceed.

First I opened the Microsoft.WebDeployment.targets file provided by they Web Deployment project. The default location of this file is C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0\. I copied the target xml for resolving references to the clipboard.

Next I opened my solution, right clicked on my web deployment project and selected Open Project File. This opens the MSBuild script file for the specific project I wish to override a target in. This is an important distinction because placing the override in the main TFSBuild.proj file is to late in the build process and will net you nothing.

I then pasted my new target into the project file near the end. My new target looks like this:

    <Target Name="_ResolveReferences" 
            DependsOnTargets="_PrepareForBuild;GetFrameworkPathAndRedistList">
        <Message Text="Overriden in NAMESPACE.Service Web Deployment Project." />
        <CreateItem Include="$(_FullSourceWebDir)\Bin\*.refresh">
            <Output TaskParameter="Include" ItemName="References_RefreshFile" />
        </CreateItem>
        <ReadLinesFromFile File="%(References_RefreshFile.Identity)" 
                           Condition=" '%(References_RefreshFile.Identity)' != '' ">
            <Output TaskParameter="Lines" ItemName="References_ReferenceRelPath" />
        </ReadLinesFromFile>
        <CombinePath BasePath="$(_FullSourceWebDir)" Paths="@(References_ReferenceRelPath)">
            <Output TaskParameter="CombinedPaths" ItemName="References" />
        </CombinePath>
        <Copy SourceFiles="@(References->'%(FullPath)')" 
              DestinationFolder="$(_FullSourceWebDir)\Bin\" 
              Condition="!Exists('%(References.Identity)')" ContinueOnError="true" />
        <ResolveAssemblyReference Assemblies="@(References->'%(FullPath)')" 
                                  TargetFrameworkDirectories="$(TargetFrameworkDirectory)" 
                                  InstalledAssemblyTables="@(InstalledAssemblyTables)" 
                                  SearchPaths="{RawFileName};{TargetFrameworkDirectory};{GAC}" 
                                  FindDependencies="true" 
                                  FindSatellites="true" 
                                  FindSerializationAssemblies="true" 
                                  FindRelatedFiles="true" 
                                  Condition="Exists('%(References.Identity)')">
            <Output TaskParameter="CopyLocalFiles" ItemName="References_CopyLocalFiles" />
        </ResolveAssemblyReference>
        <Copy SourceFiles="@(References_CopyLocalFiles)" 
              DestinationFiles="@(References_CopyLocalFiles->'$(_FullSourceWebDir)\Bin\%(DestinationSubDirectory)%(Filename)%(Extension)')" />
    </Target>

Note that this is the exact logic from the web deployment targets file. I also added a message node to alert others to the fact that I have overridden this target and given the location of the override.

For this specific project the list of @(References) that is generated by this script incorrectly maps my two messaging libraries. I have *.refresh files for them, but they point to a common folder on the developers machine that does not exist on the build server. To remedy this I added the following code just after the call to CombinePath that generates the @(References) list and just prior to the Copy element:

<CreateItem Include="D:\Build\Build Common\Binaries\Release\Lib.*.dll">
  <Output TaskParameter="Include" ItemName="Messaging_Libraries"/>
</CreateItem>
<CreateItem Include="@(References);@(Messaging_Libraries)">
  <Output TaskParameter="Include" ItemName="NewReferences"/>
</CreateItem>

This allowed my build to successfully complete on the build server. Oddly enough, in the Build steps list the Compiling sources line item has a red X icon even though the compile had no problems. Trolling through the BuildLog I found that the Copy node was throwing errors that were converted to warnings, because the original list of references contains incorrect paths for the messaging DLLs and it fails to copy them.

The copy node seems to be redundant logically, so I commented it out and my build script is complete and working again.

Next on the agenda, getting Team Build to not download my entire source tree to build a single solution. As I add things to source control my build times are getting a bit crazy.


 
Categories: Development | MSBuild

Over the course of the last week or so I have been working on migrating our source code over to TFS. In the process, I am reformulating our solutions, cleaning up garbage and creating automated build scripts.

Our application has five website projects and a hand full of supporting libraries. Using the Web Deployment Project addon, I was able to automate the build of these sites into a clean reproducible deployment.

My application is set up in source code in such a way that frameworks are separated from the actual application code. From the Main branch I have a Framework folder and an Application folder. The Framework folder contains the source code for the Enterprise Library Application Blocks, a vendor supplied library and a Lib folder for interop assemblies and various other compiled resources.

The Frameworks branch has its own build script. The idea is a developer opens up TFS finds the latest successful build of the framework and copies those assemblies to his local machine @ D:\Common. The goal being we are all using the same build of the framework libs and the developer doesn't need to worry about the source for frameworks unless a bug is identified. All applications that use framework assemblies have references set to the common folder.

After successfully migrating our web application over to TFS and getting the build working successfully, I moved on to migrating the applications windows services. These services are in their own solution and I plan to locate them into the application folder in TFS.

While reviewing the windows services solution, I noticed several applications that use messaging libraries that live in the web application solution. I decided to pull these two libraries out into their own solution and add them to the Framework build.

This is where the problems started. I was able to pull the libraries out and add them to the Framework build, but now any website project that references these libraries fails to build on the build server. The solution builds fine locally as I have *.refresh files that point to the Common folder.

Trolling through the BuildLog.txt it is pretty obvious why my build is failing. Target _ResolveReferences which is defined in Microsoft.WebDeployment.targets installed by the Web Deployment Project addon is not able to locate my two messaging assemblies.

My master build script has an AdditionalReferencePath defined, but the Web Deployment project targets seem to ignore this. I have attempted to copy the assemblies to the proper location at various points in the process (AfterGet, BeforeBuild) and have not had success. The libraries are in the proper place but because they reference other assemblies that need to be resolved the build fails.

I need to modify my build script so that it copies the libraries to the correct location and then resolves all references prior to build. This is my task for the day.


 
Categories: Development | MSBuild