The Windows Installer XML (WiX) is a toolset that builds Windows installation packages from XML source code. The toolset supports a command line environment that developers may integrate into their build processes to build MSI and MSM setup packages.

This tool properly handles discovering all of the dependencies in the WiX source files. It can follow include files and all elements in the WiX schema which reference files that get pulled into the Windows installer package or merge module. One thing to keep in mind is that the path specified in the 'src' attribute of WiX source files is assumed to be relative to the SCons root (where your SConstruct file is located).

I have not tried working with Windows Installer patch files yet, but it should be easy to extend this tool to handle them. The tool also does not try validating the WiX source file against the WiX schema, it is assumed that the WiX compilers will flag any errors with the source files.

Here is a very simple example using the tool to build a merge module:

#!pythonenv.WiX('module.msm',['module.wxs'])

At the end is the source for the tool itself. You will need to save this in something like 'wixtool.py', then load it into your construction environment in the standard way:

#!pythonenv.Tool('wixtool',localtoolpath)

#!python"""Tool to support WiX (Windows Installer XML toolset)http://blogs.msdn.com/robmen/http://sourceforge.net/projects/wix"""__revision__="$Revision: 1.1 $"__date__="$Date: 2004/05/21 20:44:46 $"__author__="elliot.murphy@veritas.com"__credits__=""importosimportxml.saximportSCons.DefaultsimportSCons.UtilimportSCons.Scannerdefwix_scanner(node,env,path):ext=os.path.splitext(str(node))[1]known_wix_sourcefiles=['.wxs','.wxi']ifextnotinknown_wix_sourcefiles:return[]include_files=[]other_deps=[]classMyHandler(xml.sax.handler.ContentHandler):defprocessingInstruction(self,target,data):iftarget=='include':data=str(data.strip())ifdatanotininclude_files:include_files.append(data)defstartElement(self,name,attrs):# EJM - Not sure about the Directory and DirectoryRef elements. They# both have src attributes, but I don't know enough about MSI to# decide # if that means they should contribute to our dependency# graph. For now, they are not included.## Not sure about the SFPCatalog element. I don't think groups# outside of MS will need to update the SFP catalog, so it's probably# safe to ignore for now. If someone from MS starts using this,# please update accordinglyelement_names=['File','Merge','Binary','Icon','DigitalCertificate','DigitalSignature','FileGroup','Text']ifnameinelement_names:names=attrs.getNames()if'src'innames:src=str(attrs.getValue('src'))# This part is a bit of a hack for handling relative paths# in both WiX and SCons. If the src attribute in the WiX# file contains a directory separator, we assume that it# is supposed to be a relative path from the root of the# source tree, not from the directory that the Sconscript# file is in. In order to do this, we prepend the magic ## to the path so that scons will know the path is relative# to the source tree root. The WiX compiler and linker are# invoked from the root of the source tree, so they already# treat src attributes as relative to the root of the src# tree.if'/'insrc:src='#'+srcifsrcnotinother_deps:other_deps.append(src)xml.sax.parseString(node.get_contents(),MyHandler())printinclude_files,other_depsreturninclude_files+other_depsdefgenerate(env):"""Add Builders and construction variables for WiX to an Environment."""env['WIXCANDLE']='candle.exe'env['WIXCANDLEFLAGS']=['-nologo']env['WIXCANDLEINCLUDE']=[]env['WIXCANDLECOM']='$WIXCANDLE $WIXCANDLEFLAGS -I $WIXCANDLEINCLUDE -o ${TARGET} ${SOURCE}'env['WIXLIGHT']='light.exe'env['WIXLIGHTFLAGS']=['-nologo']env['WIXLIGHTCOM']="$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}"wxi_scanner=env.Scanner(function=wix_scanner,name='WiX Scanner',skeys=['.wxs','.wxi'],recursive=1)env['SCANNERS']+=[wxi_scanner]object_builder=SCons.Builder.Builder(action='$WIXCANDLECOM',suffix='.wxiobj',src_suffix='.wxs')linker_builder=SCons.Builder.Builder(action='$WIXLIGHTCOM',src_suffix='.wxiobj',src_builder=object_builder)env['BUILDERS']['WiX']=linker_builderdefexists(env):return1# TODO: Should we do a better job of detecting?