Thursday, February 26, 2009

WIX in Distributed Build Enviornments

I am trying to get WIX integrated into our distributed continuous integration and build system.  I thought it would be simple, I was wrong.

WIX wants you to install it.  Being a program that creates installs that makes sense.  But in its .target files that let you integrate it into MSBuild it makes assumptions about where things will be based on registry keys it sets during the install.  This presents a problem.

We have a build farm, around 20 machine, we have a 50 developers, most with two machines.  Problem number 1: I don't want to co-ordinate getting a program rolled out across 100+ machines.  Problem number 2: upgrading.  I hope over time there will be updates to WIX.  We version our code every day.  When I upgrade to a new version of WIX I don't want to have to worry that if someone tries to build an older version of code it might not work with a newer version of the code.  I want to version the WIX environment right along side our code.  Can't do that with something I need to install.

So I figure I can just toss the WIX components into a folder in source control and update some paths in the .target files and everything will be fine.  Turns out that's a bit optimistic.

Lots of things in the WIX target files need to know where WIX is installed to.  Only problem is the build pulls the source down into dynamical generated temp files.  But I figured I could just use relative paths and things would be fine.

Not so much.  Right now I have just two project that need to reference WIX stuff, the actual .wixproj and the custom action project.  The custom action project is in a sub-folder of .wixproj file.  So if I change:

<usingtask taskname="HeatFile" assemblyfile="$(WixToolPath)WixUtilExtension.dll">
</usingtask>
to:
<usingtask taskname="HeatFile" assemblyfile="..\..\External\WIX\WixUtilExtension.dll">
</usingtask>

The relative path will only work for one of the projects and not the other one because the relative paths get resolved from different starting points are different.  (As a side note, using a macro that references a solution specific directory wouldn't work for the same reason, we has solutions at diffrent levels of the folder hierarchy as well.)

(So at this point I though about just changing my project directory structure so that all the projects that needed WIX would be at the same level.  But I figured that that would come back and bite me some day.)

The first solution that came to mind was that if I could find a way to specify the paths as relative to the .target file I would be all set.  And indeed I found a way to do this, but it requires a custom build task and so you end up with the same problem.

On to the final solution.  What I ended up doing was to have each project that needed to use WIX set a well known property that has the relative path from that project to the WIX files in it.

So you have to add the following to the project files that want to access WIX:

<wixpath condition=" '$(WixPath)' == '' ">
$(MSBuildprojectDirectory)\..\..\..\External\WIX\
</WixPath>


<wixcatargetspath condition=" '$(WixCATargetsPath)' == '' ">

$(WixPath)Wix.CA.targets</WixCATargetsPath>


and then in your .target files just base all the paths off of this the $(WixPath) property.  Attached are the WIX .target files modified to work this way.

Hopefully this helps you on your WIX development journey.

2 comments:

Rob Mensching said...

Brian, there is an entire topic in the WiX Help about how to integrate the WiX toolset into your daily build process here: http://wix.sourceforge.net/manual-wix3/daily_builds.htm .

Hopefully, that has enough detail to solve this very problem. If there are bugs in the documentation or build code please do open a bug so we can fix it.

Brian Willard said...

Hey Rob, thanks for the pointer. Unfortunately even with that you still have to resort to the trickery described in the article. Because we populate to temp directories we can't use a constance value for $(BinariesRoot) as described in the article. For the CI builds we could pass that value in on the command line but then developers would need to set that variable for each of their workspaces, something we would like to avoid. Which leaves using relative paths, but because they are relative to project and the projects are at different levels of the directory structures this doesn't work out for us. So we are left defining different relative paths for each project in the project. Which is a bit cludgy, but gets the job done.