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.
http://sourceforge.net/projects/wix http://blogs.msdn.com/robmen/
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:
1 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 'wixtool.py', then load it into your construction environment in the standard way:
1 env.Tool('wixtool', localtoolpath)
1 """
2 Tool to support WiX (Windows Installer XML toolset)
3 http://blogs.msdn.com/robmen/
4 http://sourceforge.net/projects/wix
5 """
6 __revision__ = "$Revision: 1.1 $"
7 __date__ = "$Date: 2004/05/21 20:44:46 $"
8 __author__ = "elliot.murphy@veritas.com"
9 __credits__ = ""
10
11 import os
12 import xml.sax
13
14 import SCons.Defaults
15 import SCons.Util
16 import SCons.Scanner
17
18 def wix_scanner(node, env, path):
19 ext = os.path.splitext(str(node))[1]
20 known_wix_sourcefiles = ['.wxs', '.wxi']
21 if ext not in known_wix_sourcefiles:
22 return []
23
24 include_files = []
25 other_deps = []
26
27 class MyHandler(xml.sax.handler.ContentHandler):
28
29 def processingInstruction(self, target, data):
30 if target == 'include':
31 data = str(data.strip())
32 if data not in include_files:
33 include_files.append(data)
34
35 def startElement(self, name, attrs):
36 # EJM - Not sure about the Directory and DirectoryRef elements. They
37 # both have src attributes, but I don't know enough about MSI to
38 # decide # if that means they should contribute to our dependency
39 # graph. For now, they are not included.
40 #
41 # Not sure about the SFPCatalog element. I don't think groups
42 # outside of MS will need to update the SFP catalog, so it's probably
43 # safe to ignore for now. If someone from MS starts using this,
44 # please update accordingly
45 element_names = [
46 'File',
47 'Merge',
48 'Binary',
49 'Icon',
50 'DigitalCertificate',
51 'DigitalSignature',
52 'FileGroup',
53 'Text'
54 ]
55
56 if name in element_names:
57 names = attrs.getNames()
58 if 'src' in names:
59 src = str(attrs.getValue('src'))
60
61 # This part is a bit of a hack for handling relative paths
62 # in both WiX and SCons. If the src attribute in the WiX
63 # file contains a directory separator, we assume that it
64 # is supposed to be a relative path from the root of the
65 # source tree, not from the directory that the Sconscript
66 # file is in. In order to do this, we prepend the magic #
67 # to the path so that scons will know the path is relative
68 # to the source tree root. The WiX compiler and linker are
69 # invoked from the root of the source tree, so they already
70 # treat src attributes as relative to the root of the src
71 # tree.
72 if '/' in src:
73 src = '#' + src
74
75 if src not in other_deps:
76 other_deps.append(src)
77
78 xml.sax.parseString(node.get_contents(), MyHandler())
79 print include_files, other_deps
80 return include_files + other_deps
81
82 def generate(env):
83 """Add Builders and construction variables for WiX to an Environment."""
84 env['WIXCANDLE'] = 'candle.exe'
85 env['WIXCANDLEFLAGS'] = ['-nologo']
86 env['WIXCANDLEINCLUDE'] = []
87 env['WIXCANDLECOM'] = '$WIXCANDLE $WIXCANDLEFLAGS -I $WIXCANDLEINCLUDE -o ${TARGET} ${SOURCE}'
88
89 env['WIXLIGHT'] = 'light.exe'
90 env['WIXLIGHTFLAGS'] = ['-nologo']
91 env['WIXLIGHTCOM'] = "$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}"
92
93 wxi_scanner = env.Scanner(
94 function = wix_scanner,
95 name = 'WiX Scanner',
96 skeys = ['.wxs', '.wxi'],
97 recursive = 1)
98
99 env['SCANNERS'] += [wxi_scanner]
100
101 object_builder = SCons.Builder.Builder(
102 action = '$WIXCANDLECOM',
103 suffix = '.wxiobj',
104 src_suffix = '.wxs')
105
106 linker_builder = SCons.Builder.Builder(
107 action = '$WIXLIGHTCOM',
108 src_suffix = '.wxiobj',
109 src_builder = object_builder)
110
111 env['BUILDERS']['WiX'] = linker_builder
112
113 def exists(env):
114 return 1 # TODO: Should we do a better job of detecting?
