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