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 |
import SCons.Sig |
28 |
import os.path |
29 |
import glob |
30 |
|
31 |
|
32 |
def nsis_parse( sources, keyword, multiple ): |
33 |
""" |
34 |
A function that knows how to read a .nsi file and figure |
35 |
out what files are referenced, or find the 'OutFile' line. |
36 |
|
37 |
|
38 |
sources is a list of nsi files. |
39 |
keyword is the command ('File' or 'OutFile') to look for |
40 |
multiple is true if you want all the args as a list, false if you |
41 |
just want the first one. |
42 |
""" |
43 |
stuff = [] |
44 |
for s in sources: |
45 |
c = s.get_contents() |
46 |
for l in c.split('\n'): |
47 |
semi = l.find(';') |
48 |
if (semi != -1): |
49 |
l = l[:semi] |
50 |
hash = l.find('#') |
51 |
if (hash != -1): |
52 |
l = l[:hash] |
53 |
# Look for the keyword |
54 |
l = l.strip() |
55 |
spl = l.split(None,1) |
56 |
if len(spl) > 1: |
57 |
if spl[0].capitalize() == keyword.capitalize(): |
58 |
arg = spl[1] |
59 |
if arg.startswith('"') and arg.endswith('"'): |
60 |
arg = arg[1:-1] |
61 |
if multiple: |
62 |
stuff += [ arg ] |
63 |
else: |
64 |
return arg |
65 |
return stuff |
66 |
|
67 |
|
68 |
def nsis_path( filename, nsisdefines, rootdir ): |
69 |
""" |
70 |
Do environment replacement, and prepend with the SCons root dir if |
71 |
necessary |
72 |
""" |
73 |
# We can't do variables defined by NSIS itself (like $INSTDIR), |
74 |
# only user supplied ones (like ${FOO}) |
75 |
varPos = filename.find('${') |
76 |
while varPos != -1: |
77 |
endpos = filename.find('}',varPos) |
78 |
assert endpos != -1 |
79 |
if not nsisdefines.has_key(filename[varPos+2:endpos]): |
80 |
raise KeyError ("Could not find %s in NSISDEFINES" % filename[varPos+2:endpos]) |
81 |
val = nsisdefines[filename[varPos+2:endpos]] |
82 |
if type(val) == list: |
83 |
if varPos != 0 or endpos+1 != len(filename): |
84 |
raise Exception("Can't use lists on variables that aren't complete filenames") |
85 |
return val |
86 |
filename = filename[0:varPos] + val + filename[endpos+1:] |
87 |
varPos = filename.find('${') |
88 |
return filename |
89 |
|
90 |
|
91 |
def nsis_scanner( node, env, path ): |
92 |
""" |
93 |
The scanner that looks through the source .nsi files and finds all lines |
94 |
that are the 'File' command, fixes the directories etc, and returns them. |
95 |
""" |
96 |
nodes = node.rfile() |
97 |
if not node.exists(): |
98 |
return [] |
99 |
nodes = [] |
100 |
source_dir = node.get_dir() |
101 |
for include in nsis_parse([node],'file',1): |
102 |
exp = nsis_path(include,env['NSISDEFINES'],source_dir) |
103 |
if type(exp) != list: |
104 |
exp = [exp] |
105 |
for p in exp: |
106 |
for filename in glob.glob( os.path.abspath( |
107 |
os.path.join(str(source_dir),p))): |
108 |
# Why absolute path? Cause it breaks mysteriously without it :( |
109 |
nodes.append(filename) |
110 |
return nodes |
111 |
|
112 |
|
113 |
def nsis_emitter( source, target, env ): |
114 |
""" |
115 |
The emitter changes the target name to match what the command actually will |
116 |
output, which is the argument to the OutFile command. |
117 |
""" |
118 |
nsp = nsis_parse(source,'outfile',0) |
119 |
if not nsp: |
120 |
return (target,source) |
121 |
x = ( |
122 |
nsis_path(nsp,env['NSISDEFINES'],''), |
123 |
source) |
124 |
return x |
125 |
|
126 |
def quoteIfSpaced(text): |
127 |
if ' ' in text: |
128 |
return '"'+text+'"' |
129 |
else: |
130 |
return text |
131 |
|
132 |
def toString(item,env): |
133 |
if type(item) == list: |
134 |
ret = '' |
135 |
for i in item: |
136 |
if ret: |
137 |
ret += ' ' |
138 |
val = toString(i,env) |
139 |
if ' ' in val: |
140 |
val = "'"+val+"'" |
141 |
ret += val |
142 |
return ret |
143 |
else: |
144 |
# For convienence, handle #s here |
145 |
if str(item).startswith('#'): |
146 |
item = env.File(item).get_abspath() |
147 |
return str(item) |
148 |
|
149 |
def runNSIS(source,target,env,for_signature): |
150 |
ret = env['NSIS']+" " |
151 |
if env.has_key('NSISFLAGS'): |
152 |
for flag in env['NSISFLAGS']: |
153 |
ret += flag |
154 |
ret += ' ' |
155 |
if env.has_key('NSISDEFINES'): |
156 |
for d in env['NSISDEFINES']: |
157 |
ret += '/D'+d |
158 |
if env['NSISDEFINES'][d]: |
159 |
ret +='='+quoteIfSpaced(toString(env['NSISDEFINES'][d],env)) |
160 |
ret += ' ' |
161 |
for s in source: |
162 |
ret += quoteIfSpaced(str(s)) |
163 |
return ret |
164 |
|
165 |
def generate(env): |
166 |
""" |
167 |
This function adds NSIS support to your environment. |
168 |
""" |
169 |
env['BUILDERS']['Installer'] = SCons.Builder.Builder(generator=runNSIS, |
170 |
src_suffix='.nsi', |
171 |
emitter=nsis_emitter) |
172 |
env.Append(SCANNERS = SCons.Scanner.Scanner( function = nsis_scanner, |
173 |
skeys = ['.nsi'])) |
174 |
if not env.has_key('NSISDEFINES'): |
175 |
env['NSISDEFINES'] = {} |
176 |
env['NSIS'] = find_nsis(env) |
177 |
|
178 |
def find_nsis(env): |
179 |
""" |
180 |
Try and figure out if NSIS is installed on this machine, and if so, |
181 |
where. |
182 |
""" |
183 |
if SCons.Util.can_read_reg: |
184 |
# If we can read the registry, get the NSIS command from it |
185 |
try: |
186 |
k = SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, |
187 |
'SOFTWARE\\NSIS') |
188 |
val, tok = SCons.Util.RegQueryValueEx(k,None) |
189 |
ret = val + os.path.sep + 'makensis.exe' |
190 |
if os.path.exists(ret): |
191 |
return '"' + ret + '"' |
192 |
else: |
193 |
return None |
194 |
except: |
195 |
pass # Couldn't find the key, just act like we can't read the registry |
196 |
# Hope it's on the path |
197 |
return env.WhereIs('makensis.exe') |
198 |
|
199 |
def exists(env): |
200 |
""" |
201 |
Is NSIS findable on this machine? |
202 |
""" |
203 |
if find_nsis(env) != None: |
204 |
return 1 |
205 |
return 0 |