Clone wiki

SCons / WiX_Tool

The SCons wiki has moved to

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:

env.WiX('module.msm', ['module.wxs'])

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

env.Tool('wixtool', localtoolpath)
Tool to support WiX (Windows Installer XML toolset)
__revision__ = "$Revision: 1.1 $"
__date__ = "$Date: 2004/05/21 20:44:46 $"
__author__ = ""
__credits__ = ""

import os
import xml.sax

import SCons.Defaults
import SCons.Util
import SCons.Scanner

def wix_scanner(node, env, path):
    ext = os.path.splitext(str(node))[1]
    known_wix_sourcefiles = ['.wxs', '.wxi']
    if ext not in known_wix_sourcefiles:
        return []

    include_files = []
    other_deps = []

    class MyHandler(xml.sax.handler.ContentHandler):

        def processingInstruction(self, target, data):
            if target == 'include':
                data = str(data.strip())
                if data not in include_files:

        def startElement(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 accordingly
            element_names = [

            if name in element_names:
                names = attrs.getNames()
                if 'src' in names:
                    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 '/' in src:
                        src = '#' + src

                    if src not in other_deps:

    xml.sax.parseString(node.get_contents(), MyHandler())
    print include_files, other_deps
    return include_files + other_deps

def generate(env):
    """Add Builders and construction variables for WiX to an Environment."""
    env['WIXCANDLE'] = 'candle.exe'
    env['WIXCANDLEFLAGS'] = ['-nologo']
    env['WIXCANDLEINCLUDE'] = []

    env['WIXLIGHT'] = 'light.exe'
    env['WIXLIGHTFLAGS'] = ['-nologo']

    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_builder

def exists(env):
    return 1 # TODO: Should we do a better job of detecting?