1 | """ |
---|
2 | Create a Windows installer EXE. |
---|
3 | |
---|
4 | usage: create_win_installer [<revision>] |
---|
5 | |
---|
6 | where <revision> is an optional revision number - if not supplied then |
---|
7 | update through svn and use latest revision |
---|
8 | |
---|
9 | This program expects to be run from the root directory of the ANUGA branch |
---|
10 | being released. |
---|
11 | |
---|
12 | This script works only on Windows! |
---|
13 | """ |
---|
14 | |
---|
15 | |
---|
16 | import sys |
---|
17 | import os |
---|
18 | import getopt |
---|
19 | import tempfile |
---|
20 | import shutil |
---|
21 | |
---|
22 | import dirs_to_distribute |
---|
23 | from anuga.utilities.data_audit_wrapper import IP_verified |
---|
24 | |
---|
25 | from anuga.utilities.system_tools import get_user_name, get_host_name |
---|
26 | from anuga.abstract_2d_finite_volumes.util import get_revision_number |
---|
27 | from anuga.abstract_2d_finite_volumes.util import store_version_info |
---|
28 | from anuga.config import major_revision |
---|
29 | |
---|
30 | |
---|
31 | # name of 'home' directory containing releases |
---|
32 | ANUGA_RELEASE_DIR = 'anuga_releases' |
---|
33 | |
---|
34 | # prefix for temporary directory |
---|
35 | TEMP_DIR_PREFIX = 'anuga_release_' |
---|
36 | |
---|
37 | # path to and required contents of documentation directory |
---|
38 | DOC_PATH = os.path.join('anuga_core', 'documentation', 'user_manual') |
---|
39 | DOC_FILES = ['anuga_user_manual.pdf', |
---|
40 | 'anuga_installation_guide.pdf', |
---|
41 | 'anuga_whats_new.pdf'] |
---|
42 | |
---|
43 | |
---|
44 | # path to NSIS install directory |
---|
45 | NSIS_PATH = r'C:\Program Files\NSIS' |
---|
46 | NSIS_EXE = os.path.join(NSIS_PATH, 'makensis.exe') |
---|
47 | |
---|
48 | # the name of the subversion commandline client |
---|
49 | SVN_CLIENT = 'svn' |
---|
50 | |
---|
51 | |
---|
52 | def abort(msg, stop=True): |
---|
53 | """Some sort of error, report and abort.""" |
---|
54 | |
---|
55 | xlog('*' * 80) |
---|
56 | xlog(msg) |
---|
57 | xlog('*' * 80) |
---|
58 | if stop: |
---|
59 | sys.exit(10) |
---|
60 | |
---|
61 | |
---|
62 | def check_doc(temp_dir, release_name): |
---|
63 | """Make sure all the documentation is present. |
---|
64 | |
---|
65 | temp_dir released code working directory |
---|
66 | release_name ANUGA release name |
---|
67 | |
---|
68 | Returns if everything OK. |
---|
69 | """ |
---|
70 | |
---|
71 | bad = False |
---|
72 | |
---|
73 | for file in DOC_FILES: |
---|
74 | doc_file = os.path.join(DOC_PATH, file) |
---|
75 | if not os.path.isfile(doc_file): |
---|
76 | xlog('Documentation file %s is missing.' % doc_file) |
---|
77 | bad = True |
---|
78 | |
---|
79 | if bad and not Force: |
---|
80 | abort('Sorry, documentation files are missing.\n' |
---|
81 | 'You must release on Linux before Windows.') |
---|
82 | |
---|
83 | |
---|
84 | def check_IP(temp_dir, verbose=False): |
---|
85 | """Check IP statements in release directories.""" |
---|
86 | |
---|
87 | if not IP_verified(temp_dir, verbose=verbose): |
---|
88 | if not Force: |
---|
89 | abort('Files have not been verified for IP.\n' |
---|
90 | 'Each data file must have a licence file with it.') |
---|
91 | |
---|
92 | |
---|
93 | def compile_nsi_file(): |
---|
94 | """Compile the installer NSI file.""" |
---|
95 | |
---|
96 | # figure out where the *.nsi file actually is |
---|
97 | nsi_file = os.path.join(BranchDir, 'installation_files', 'windows', |
---|
98 | 'installer.nsi') |
---|
99 | |
---|
100 | do_cmd('"%s" /V2 %s' % (NSIS_EXE, nsi_file)) |
---|
101 | |
---|
102 | |
---|
103 | def copy_release_directories(revision, temp_dir): |
---|
104 | """Get release dirs (correct version) into release directory.""" |
---|
105 | |
---|
106 | for src in dirs_to_distribute.dirmap: |
---|
107 | dst = os.path.join(temp_dir, dirs_to_distribute.dirmap[src]) |
---|
108 | do_cmd('%s export -r %s --quiet %s %s' |
---|
109 | % (SVN_CLIENT, revision, src, dst)) |
---|
110 | |
---|
111 | |
---|
112 | def do_cmd(cmd, ignore_errors=False): |
---|
113 | """Execute a command, optionally ignore errors.""" |
---|
114 | |
---|
115 | try: |
---|
116 | xlog(cmd) |
---|
117 | os.system(cmd) |
---|
118 | except: |
---|
119 | if not ignore_errors: |
---|
120 | raise |
---|
121 | |
---|
122 | |
---|
123 | def get_directories(rel_name): |
---|
124 | """Create release and temporary directories, return paths. |
---|
125 | |
---|
126 | rel_name the ANUGA release name |
---|
127 | """ |
---|
128 | |
---|
129 | # create the release directory |
---|
130 | rel_area = os.path.join('~', ANUGA_RELEASE_DIR) |
---|
131 | rel_area = os.path.expanduser(rel_area) |
---|
132 | |
---|
133 | rel_dir = os.path.join(rel_area, rel_name) |
---|
134 | if os.path.isdir(rel_dir): |
---|
135 | if Force: |
---|
136 | os.removedirs(rel_dir) |
---|
137 | else: |
---|
138 | abort("Sorry, release directory '%s' already exists.\n" |
---|
139 | "Please delete that directory first." % rel_dir) |
---|
140 | |
---|
141 | os.makedirs(rel_dir) |
---|
142 | |
---|
143 | # create a temporary scratch directory |
---|
144 | temp_dir = tempfile.mkdtemp(suffix='', prefix=TEMP_DIR_PREFIX) |
---|
145 | |
---|
146 | return (rel_dir, temp_dir) |
---|
147 | |
---|
148 | |
---|
149 | def get_release_name(): |
---|
150 | """Get release information and create release name. |
---|
151 | |
---|
152 | Get release numbers from the current directory. |
---|
153 | |
---|
154 | Returns a string which is of the form 'anuga-X.Y.Z' where X is the |
---|
155 | major release number, Y is the minor and Z is the bug number. |
---|
156 | """ |
---|
157 | |
---|
158 | curr_dir = os.getcwd() |
---|
159 | curr_dir = os.path.basename(curr_dir) |
---|
160 | split_dir = curr_dir.split('_') |
---|
161 | if len(split_dir) < 2: |
---|
162 | abort('You must run this script in an ANUGA branch directory.') |
---|
163 | if split_dir[0] != 'anuga': |
---|
164 | abort('You must run this script in an ANUGA branch directory.') |
---|
165 | |
---|
166 | major = split_dir[1] |
---|
167 | if len(split_dir) < 3: |
---|
168 | minor = '0' |
---|
169 | else: |
---|
170 | minor = split_dir[2] |
---|
171 | |
---|
172 | if len(split_dir) < 4: |
---|
173 | bug = '0' |
---|
174 | else: |
---|
175 | bug = split_dir[3] |
---|
176 | |
---|
177 | return 'anuga-%s.%s.%s' % (major, minor, bug) |
---|
178 | |
---|
179 | |
---|
180 | def get_svn_revision(): |
---|
181 | """Get the current revision number from svn. |
---|
182 | |
---|
183 | This uses the CollabNet subversion client. |
---|
184 | """ |
---|
185 | |
---|
186 | try: |
---|
187 | fid = os.popen('%s info' % SVN_CLIENT) |
---|
188 | except: |
---|
189 | abort('Subversion commandline client not found') |
---|
190 | |
---|
191 | lines = fid.readlines() |
---|
192 | fid.close() |
---|
193 | |
---|
194 | for l in lines: |
---|
195 | l = l.strip() |
---|
196 | if l.startswith('Last Changed Rev:'): |
---|
197 | (_, revision) = l.split(':', 1) |
---|
198 | try: |
---|
199 | revision = int(revision) |
---|
200 | except ValueError: |
---|
201 | abort('"svn info" did not return expected line: ' + l) |
---|
202 | return revision |
---|
203 | |
---|
204 | abort('"svn info" did not return expected line "Last Changed Rev:"') |
---|
205 | |
---|
206 | |
---|
207 | def make_installer(revision): |
---|
208 | """Make a windows installer. |
---|
209 | |
---|
210 | revision the revision number (string) to release |
---|
211 | (may be None, meaning 'use latest') |
---|
212 | """ |
---|
213 | |
---|
214 | global BranchDir |
---|
215 | |
---|
216 | # get the current branch directory path |
---|
217 | BranchDir = os.getcwd() |
---|
218 | |
---|
219 | # if revision is None (not supplied) do update and get latest revision |
---|
220 | if revision is None: |
---|
221 | do_cmd('%s up' % SVN_CLIENT) |
---|
222 | revision = get_svn_revision() |
---|
223 | |
---|
224 | # get release name from current directory |
---|
225 | release_name = get_release_name() |
---|
226 | |
---|
227 | # get working directory paths (directories have been created) |
---|
228 | (release_dir, temp_dir) = get_directories(release_name) |
---|
229 | |
---|
230 | # get the ANUGA directories that are being released |
---|
231 | copy_release_directories(revision, temp_dir) |
---|
232 | |
---|
233 | # check IP data for release directories |
---|
234 | check_IP(temp_dir, verbose=True) |
---|
235 | |
---|
236 | # generate the LaTeX documentation |
---|
237 | check_doc(temp_dir, release_name) |
---|
238 | |
---|
239 | # create ZIP release file, put into release dir |
---|
240 | make_zip_file(temp_dir, release_name, release_dir) |
---|
241 | |
---|
242 | # compile Windows *.nsi file |
---|
243 | # creates EXE in <BranchDir>\installation_files\windows |
---|
244 | # check we have required files/directories |
---|
245 | # remove all generated *.exe files |
---|
246 | anuga_dir = os.path.join(BranchDir, 'installation_files', 'windows', |
---|
247 | 'files', release_name) |
---|
248 | if not os.path.isdir(anuga_dir): |
---|
249 | abort("Sorry, directory %s doesn't exist.\n" |
---|
250 | "You need to run the Linux create distribution first." |
---|
251 | % anuga_dir) |
---|
252 | |
---|
253 | |
---|
254 | src = os.path.join(BranchDir, 'installation_files', 'windows', '*.exe') |
---|
255 | do_cmd('del /Q %s' % src) |
---|
256 | compile_nsi_file() |
---|
257 | |
---|
258 | # copy the create EXE to the release directory |
---|
259 | do_cmd('move /Y %s %s' % (src, release_dir)) |
---|
260 | |
---|
261 | # clean up |
---|
262 | shutil.rmtree(temp_dir) |
---|
263 | |
---|
264 | |
---|
265 | def make_zip_file(temp_dir, release_name, release_dir): |
---|
266 | """Make a ZIP file, copy to release directory. |
---|
267 | |
---|
268 | temp_dir directory where we copied release files |
---|
269 | release_name the release name |
---|
270 | release_dir destination for the ZIP file |
---|
271 | """ |
---|
272 | |
---|
273 | # create .tgz file in temp_dir |
---|
274 | |
---|
275 | |
---|
276 | |
---|
277 | def usage(msg=None): |
---|
278 | """Give befuddled user some help.""" |
---|
279 | |
---|
280 | if msg: |
---|
281 | print(msg) |
---|
282 | print('usage: create_win_installer [<revision_number>]\n' |
---|
283 | 'where <revision_number> is an optional revision number to release.') |
---|
284 | |
---|
285 | |
---|
286 | def xlog(msg): |
---|
287 | """Print and log a message.""" |
---|
288 | |
---|
289 | print(msg) |
---|
290 | |
---|
291 | # uncomment this to get file logging |
---|
292 | ## log(msg) |
---|
293 | |
---|
294 | |
---|
295 | if __name__ == '__main__': |
---|
296 | global Force |
---|
297 | |
---|
298 | if sys.platform != 'win32': |
---|
299 | msg = ('This script is not written for Linux. ' |
---|
300 | 'Please run it on a Windows platform.') |
---|
301 | raise Exception(msg) |
---|
302 | |
---|
303 | argv = sys.argv[1:] |
---|
304 | |
---|
305 | try: |
---|
306 | opts, args = getopt.getopt(argv, 'hf', ['help', 'force']) |
---|
307 | except getopt.error, msg: |
---|
308 | usage() |
---|
309 | sys.exit(10) |
---|
310 | |
---|
311 | Force = False |
---|
312 | for (opt, param) in opts: |
---|
313 | if opt in ['-h', '--help']: |
---|
314 | usage() |
---|
315 | sys.exit(0) |
---|
316 | elif opt in ['-f', '--force']: |
---|
317 | Force = True |
---|
318 | |
---|
319 | if len(args) > 1: |
---|
320 | usage() |
---|
321 | sys.exit(10) |
---|
322 | |
---|
323 | revision = None |
---|
324 | if len(args) == 1: |
---|
325 | try: |
---|
326 | revision = int(args[0]) |
---|
327 | except ValueError: |
---|
328 | usage() |
---|
329 | sys.exit(10) |
---|
330 | |
---|
331 | make_installer(revision) |
---|