This is a Tool for SCons that can parse a Nullsoft Scriptable Installation System (NSIS) script (a .nsi file) and determine its dependencies and its actual target name. The following file can be be copied to site-packages/scons/SCons/Tools/ and it will 'just work'.
Enhancement note: This uses glob.glob to find the source files if they have filename glob chars, but if (as is usual) the sources are built by some other builder, you might want to use one of the techniques in BuildDirGlob to find them instead, so scons gets all the dependencies correct.
Feel free to modify and improve as you wish.
UPDATE: I have patched this tool to work correctly on Windows 64-bit (with Python 64 bit). If this is useful to you, see here: http://code.ascend4.org/viewvc/code/trunk/scons/nsis.py?view=log. -- JohnPye
Works for me too! Rather than installing the file in site-packages, I've used it as embedded code in my main trunk/SConstruct and trunk/pygtk/interface/SConscript, which are visible at https://pse.cheme.cmu.edu/svn-view/ascend/code/trunk/, so that I don't have to tell users to mess with their default SCons installation. -- JohnPye
I have made changes that allow using makensis in linux, and also basic analysis of !include and !ifdef directives (not included in the script below, which does parse File operations). These changes are provided in a diff. I have also moved to using env.Glob() instead of glob.glob. The final script, with those changes applied to the January 2004 shown below, is here. I did not test the script with Windows makensis.exe so there is a possibility some things were broken there. The only thing I can think of that has possibly been broken, however, is the determination of the SCons install directory in line 146. To use as a tool without overwriting the SCons install directory, I placed it in a subdirectory of the SConstruct directory called "scons_tools" and used:
env.Tool("nsis", toolpath=["scons_tools"])-- YitzhakSapir
The original script for the tool is provided below.
1 # NSIS Support for SCons
2 # Written by Mike Elkins, January 2004
3 # Provided 'as-is', it works for me!
4
5
6 """
7 This tool provides SCons support for the Nullsoft Scriptable Install System
8 a windows installer builder available at http://nsis.sourceforge.net/home
9
10
11 To use it you must copy this file into the scons/SCons/Tools directory or use
12 the tooldir arg in the Tool function and put a line like 'env.Tool("NSIS")'
13 into your file. Then you can do 'env.Installer("foobar")' which will read foobar.nsi and
14 create dependencies on all the files you put into your installer, so that if
15 anything changes your installer will be rebuilt. It also makes the target
16 equal to the filename you specified in foobar.nsi. Wildcards are handled correctly.
17
18 In addition, if you set NSISDEFINES to a dictionary, those variables will be passed
19 to NSIS.
20 """
21
22
23
24 import SCons.Builder
25 import SCons.Util
26 import SCons.Scanner
27 # NOTE (4 September 2007): The following import line was part of the original
28 # code on this wiki page before this date. It's not used anywhere below and
29 # therefore unnecessary. The SCons.Sig module is going away after 0.97.0d20070809,
30 # so the line should be removed from your copy of this module. There may be a
31 # do-nothing SCons.Sig module that generates a warning message checked in, so existing
32 # configurations won't break and can help point people to the line that needs removing.
33 #import SCons.Sig
34 import os.path
35 import glob
36
37
38 def nsis_parse( sources, keyword, multiple ):
39 """
40 A function that knows how to read a .nsi file and figure
41 out what files are referenced, or find the 'OutFile' line.
42
43
44 sources is a list of nsi files.
45 keyword is the command ('File' or 'OutFile') to look for
46 multiple is true if you want all the args as a list, false if you
47 just want the first one.
48 """
49 stuff = []
50 for s in sources:
51 c = s.get_contents()
52 for l in c.split('\n'):
53 semi = l.find(';')
54 if semi != -1:
55 l = l[:semi]
56 hash = l.find('#')
57 if hash != -1:
58 l = l[:hash]
59 # Look for the keyword
60 l = l.strip()
61 spl = l.split(None,1)
62 if len(spl) > 1:
63 if spl[0].lower() == keyword.lower():
64 arg = spl[1]
65 if arg.startswith('"') and arg.endswith('"'):
66 arg = arg[1:-1]
67 if multiple:
68 stuff += [ arg ]
69 else:
70 return arg
71 return stuff
72
73
74 def nsis_path( filename, nsisdefines, rootdir ):
75 """
76 Do environment replacement, and prepend with the SCons root dir if
77 necessary
78 """
79 # We can't do variables defined by NSIS itself (like $INSTDIR),
80 # only user supplied ones (like ${FOO})
81 varPos = filename.find('${')
82 while varPos != -1:
83 endpos = filename.find('}',varPos)
84 assert endpos != -1
85 if filename[varPos+2:endpos] not in nsisdefines:
86 raise KeyError ("Could not find %s in NSISDEFINES" % filename[varPos+2:endpos])
87 val = nsisdefines[filename[varPos+2:endpos]]
88 if type(val) == list:
89 if varPos != 0 or endpos+1 != len(filename):
90 raise Exception("Can't use lists on variables that aren't complete filenames")
91 return val
92 filename = filename[:varPos] + val + filename[endpos+1:]
93 varPos = filename.find('${')
94 return filename
95
96
97 def nsis_scanner( node, env, path ):
98 """
99 The scanner that looks through the source .nsi files and finds all lines
100 that are the 'File' command, fixes the directories etc, and returns them.
101 """
102 nodes = node.rfile()
103 if not node.exists():
104 return []
105 nodes = []
106 source_dir = node.get_dir()
107 for include in nsis_parse([node],'file',1):
108 exp = nsis_path(include,env['NSISDEFINES'],source_dir)
109 if type(exp) != list:
110 exp = [exp]
111 for p in exp:
112 for filename in glob.glob( os.path.abspath(
113 os.path.join(str(source_dir),p))):
114 # Why absolute path? Cause it breaks mysteriously without it :(
115 nodes.append(filename)
116 return nodes
117
118
119 def nsis_emitter( source, target, env ):
120 """
121 The emitter changes the target name to match what the command actually will
122 output, which is the argument to the OutFile command.
123 """
124 nsp = nsis_parse(source,'outfile',0)
125 if not nsp:
126 return (target,source)
127 x = (
128 nsis_path(nsp,env['NSISDEFINES'],''),
129 source)
130 return x
131
132 def quoteIfSpaced(text):
133 if ' ' in text:
134 return '"'+text+'"'
135 else:
136 return text
137
138 def toString(item,env):
139 if type(item) == list:
140 ret = ''
141 for i in item:
142 if ret:
143 ret += ' '
144 val = toString(i,env)
145 if ' ' in val:
146 val = "'"+val+"'"
147 ret += val
148 return ret
149 else:
150 # For convienence, handle #s here
151 if str(item).startswith('#'):
152 item = env.File(item).get_abspath()
153 return str(item)
154
155 def runNSIS(source,target,env,for_signature):
156 ret = env['NSIS']+" "
157 if 'NSISFLAGS' in env:
158 for flag in env['NSISFLAGS']:
159 ret += flag
160 ret += ' '
161 if 'NSISDEFINES' in env:
162 for d in env['NSISDEFINES']:
163 ret += '/D'+d
164 if env['NSISDEFINES'][d]:
165 ret +='='+quoteIfSpaced(toString(env['NSISDEFINES'][d],env))
166 ret += ' '
167 for s in source:
168 ret += quoteIfSpaced(str(s))
169 return ret
170
171 def generate(env):
172 """
173 This function adds NSIS support to your environment.
174 """
175 env['BUILDERS']['Installer'] = SCons.Builder.Builder(generator=runNSIS,
176 src_suffix='.nsi',
177 emitter=nsis_emitter)
178 env.Append(SCANNERS = SCons.Scanner.Scanner( function = nsis_scanner,
179 skeys = ['.nsi']))
180 if 'NSISDEFINES' not in env:
181 env['NSISDEFINES'] = {}
182 env['NSIS'] = find_nsis(env)
183
184 def find_nsis(env):
185 """
186 Try and figure out if NSIS is installed on this machine, and if so,
187 where.
188 """
189 if SCons.Util.can_read_reg:
190 # If we can read the registry, get the NSIS command from it
191 try:
192 k = SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
193 'SOFTWARE\\NSIS')
194 val, tok = SCons.Util.RegQueryValueEx(k,None)
195 ret = val + os.path.sep + 'makensis.exe'
196 if os.path.exists(ret):
197 return '"' + ret + '"'
198 else:
199 return None
200 except:
201 pass # Couldn't find the key, just act like we can't read the registry
202 # Hope it's on the path
203 return env.WhereIs('makensis.exe')
204
205 def exists(env):
206 """
207 Is NSIS findable on this machine?
208 """
209 return find_nsis(env) != None:
