1 |
jpye |
1408 |
# 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 |
johnpye |
913 |
return 0 |