diff options
Diffstat (limited to 'linden/scripts')
-rwxr-xr-x | linden/scripts/package.py | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/linden/scripts/package.py b/linden/scripts/package.py new file mode 100755 index 0000000..59aef79 --- /dev/null +++ b/linden/scripts/package.py | |||
@@ -0,0 +1,338 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # | ||
3 | # @file package.py | ||
4 | # @author Jacek Antonelli | ||
5 | # @brief Script for generating viewer installer packages. | ||
6 | # | ||
7 | # Usage: package.py --build-dir=PATH [options] | ||
8 | # | ||
9 | # Copyright (c) 2007-2009, Linden Research, Inc. | ||
10 | # Copyright (c) 2010-2011, Jacek Antonelli | ||
11 | # | ||
12 | # Permission is hereby granted, free of charge, to any person | ||
13 | # obtaining a copy of this software and associated documentation files | ||
14 | # (the "Software"), to deal in the Software without restriction, | ||
15 | # including without limitation the rights to use, copy, modify, merge, | ||
16 | # publish, distribute, sublicense, and/or sell copies of the Software, | ||
17 | # and to permit persons to whom the Software is furnished to do so, | ||
18 | # subject to the following conditions: | ||
19 | # | ||
20 | # The above copyright notice and this permission notice shall be | ||
21 | # included in all copies or substantial portions of the Software. | ||
22 | # | ||
23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
24 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
25 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
26 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
27 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
28 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
29 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
30 | # SOFTWARE. | ||
31 | # | ||
32 | |||
33 | import os, sys | ||
34 | from viewer_info import ViewerInfo | ||
35 | |||
36 | |||
37 | SCRIPTS_DIR = sys.path[0] # directory containing this script | ||
38 | TOP_DIR = os.path.abspath(os.path.join(SCRIPTS_DIR,'..')) | ||
39 | SOURCE_DIR = os.path.abspath(os.path.join(TOP_DIR,'indra')) | ||
40 | BUILD_TYPE = "RelWithDebInfo" | ||
41 | |||
42 | |||
43 | class PackagerError(Exception): pass | ||
44 | |||
45 | class BadDir(PackagerError): pass | ||
46 | |||
47 | class WeirdPlatform(PackagerError): pass | ||
48 | |||
49 | class CmdFailed(PackagerError): pass | ||
50 | |||
51 | |||
52 | def indent(text, amount=4): | ||
53 | import string | ||
54 | lines = [(' '*amount + line) for line in string.split(text, '\n')] | ||
55 | return string.join(lines, '\n') | ||
56 | |||
57 | def message(*args): | ||
58 | """Prints an informational message with a leading '#'.""" | ||
59 | print '# ' + ' '.join([str(arg) for arg in args]) | ||
60 | |||
61 | def error(*args): | ||
62 | """Prints an error message to stderr.""" | ||
63 | print >> sys.stderr, 'Error: ' + ' '.join([str(arg) for arg in args]) | ||
64 | |||
65 | |||
66 | class Packager: | ||
67 | |||
68 | def __init__(self, build_dir, opts={}): | ||
69 | options = {'source_dir': SOURCE_DIR, | ||
70 | 'build_type': BUILD_TYPE, | ||
71 | 'verbose': False, | ||
72 | } | ||
73 | options.update(opts) | ||
74 | |||
75 | self.build_dir = os.path.abspath(build_dir) | ||
76 | self.__check_build_dir() | ||
77 | |||
78 | # Package results go in the top build directory. | ||
79 | self.dest_dir = build_dir | ||
80 | |||
81 | self.source_dir = os.path.abspath(options['source_dir']) | ||
82 | self.__check_source_dir() | ||
83 | |||
84 | self.build_type = options['build_type'] | ||
85 | self.platform = self.__get_platform() | ||
86 | self.verbose = options['verbose'] | ||
87 | self.viewer_info = ViewerInfo() | ||
88 | |||
89 | |||
90 | def make(self): | ||
91 | plat = self.platform | ||
92 | if plat == 'linux': self.make_linux() | ||
93 | elif plat == 'mac': self.make_mac() | ||
94 | elif plat == 'windows': self.make_windows() | ||
95 | |||
96 | |||
97 | ######### | ||
98 | # LINUX # | ||
99 | ######### | ||
100 | |||
101 | def make_linux(self): | ||
102 | import shutil | ||
103 | |||
104 | packaged_dir = os.path.join(self.build_dir, 'newview', 'packaged') | ||
105 | |||
106 | if not os.path.exists(packaged_dir): | ||
107 | raise BadDir("invalid build dir, has no 'newview/packaged/' " | ||
108 | 'subdirectory: %(d)r'%{'d': self.build_dir}) | ||
109 | |||
110 | self.__run_command( 'Checking/fixing file permissions...', | ||
111 | """find %(d)r -type d | xargs --no-run-if-empty chmod 755; | ||
112 | find %(d)r -type f -perm 0700 | xargs --no-run-if-empty chmod 0755; | ||
113 | find %(d)r -type f -perm 0500 | xargs --no-run-if-empty chmod 0555; | ||
114 | find %(d)r -type f -perm 0600 | xargs --no-run-if-empty chmod 0644; | ||
115 | find %(d)r -type f -perm 0400 | xargs --no-run-if-empty chmod 0444; | ||
116 | true""" % {'d': packaged_dir}) | ||
117 | |||
118 | plat = 'Linux' | ||
119 | from platform import architecture | ||
120 | if architecture()[0] == '64bit': | ||
121 | plat += '-x86_64' | ||
122 | elif architecture()[0] == '32bit': | ||
123 | plat += '-x86' | ||
124 | |||
125 | inst_name = self.viewer_info.combined + '-' + plat | ||
126 | dest_file = os.path.join(self.dest_dir, inst_name + '.tar.bz2') | ||
127 | |||
128 | if (os.path.exists(dest_file)): | ||
129 | bkp = dest_file + ".bkp" | ||
130 | message("Renaming existing package to %r..." % bkp) | ||
131 | shutil.move(dest_file, bkp) | ||
132 | |||
133 | self.__run_command( | ||
134 | 'Creating package %r (this takes a while)...'%dest_file, | ||
135 | 'tar -C %(dir)s --numeric-owner ' | ||
136 | '--transform "s,^./,%(inst_name)s/," ' | ||
137 | #'--verbose --show-transformed-names ' | ||
138 | '-cjf %(dest_file)s .' % { 'dir': packaged_dir, | ||
139 | 'inst_name': inst_name, | ||
140 | 'dest_file': dest_file}) | ||
141 | |||
142 | message('Package complete: %r' % dest_file) | ||
143 | |||
144 | |||
145 | ####### | ||
146 | # MAC # | ||
147 | ####### | ||
148 | |||
149 | def make_mac(self): | ||
150 | import shutil | ||
151 | |||
152 | volname = self.viewer_info.name + " Installer" | ||
153 | |||
154 | # Where the DMG files (background image, etc.) come from. | ||
155 | dmg_src = os.path.join(self.source_dir, 'newview', 'packaging', 'mac') | ||
156 | |||
157 | # Everything that will be in the package is copied to here. | ||
158 | dmg_dst = os.path.join('/Volumes', volname) | ||
159 | |||
160 | if (os.path.exists(dmg_dst)): | ||
161 | error('%r is currently attached. Eject it and try again.' % dmg_dst) | ||
162 | sys.exit(1) | ||
163 | |||
164 | app_name = self.viewer_info.name + ".app" | ||
165 | app_orig = os.path.join(self.build_dir, 'newview', self.build_type, app_name) | ||
166 | app_dst = os.path.join(dmg_dst, app_name) | ||
167 | |||
168 | if (not os.path.exists(app_orig)): | ||
169 | error("App does not exist: %r" % app_orig) | ||
170 | sys.exit(1) | ||
171 | |||
172 | dmg_name = "%s-Mac"%(self.viewer_info.combined) | ||
173 | temp_dmg = os.path.join(self.build_dir, dmg_name+".sparseimage") | ||
174 | final_dmg = os.path.join(self.dest_dir, dmg_name+".dmg") | ||
175 | |||
176 | if (os.path.exists(temp_dmg)): | ||
177 | message("Removing stale temp disk image...") | ||
178 | os.remove(temp_dmg) | ||
179 | |||
180 | self.__run_command( | ||
181 | 'Creating temp disk image...', | ||
182 | 'hdiutil create %(temp)r -volname %(volname)r -fs HFS+ ' | ||
183 | '-layout SPUD -type SPARSE' % | ||
184 | {'temp': temp_dmg, 'volname': volname, 'src': dmg_dst}) | ||
185 | |||
186 | self.__run_command( | ||
187 | 'Mounting temp disk image...', | ||
188 | 'hdiutil attach %r -readwrite -noautoopen' % temp_dmg) | ||
189 | |||
190 | # Move the .app to the staging area (temporarily). | ||
191 | message("Copying %r (this takes a while)..."%(app_name)) | ||
192 | shutil.copytree(app_orig, app_dst, symlinks=True) | ||
193 | |||
194 | message("Copying background.png...") | ||
195 | shutil.copy2( os.path.join(dmg_src, 'background.png'), | ||
196 | os.path.join(dmg_dst, 'background.png')) | ||
197 | |||
198 | config_script = os.path.join(self.source_dir, 'newview', | ||
199 | 'packaging', 'mac', 'ConfigureDMG.scpt') | ||
200 | |||
201 | self.__run_command( | ||
202 | "Configuring temp disk image's view options...", | ||
203 | 'osascript %(script)r %(volname)r %(app_name)r' % | ||
204 | {'script': config_script, 'volname': volname, 'app_name': app_name}) | ||
205 | |||
206 | # self.__run_command( | ||
207 | # 'Hiding background.png...', | ||
208 | # 'SetFile -a V %r' % os.path.join(dmg_dst, 'background.png')) | ||
209 | |||
210 | self.__run_command( | ||
211 | 'Unmounting temp disk image...', | ||
212 | 'hdiutil detach %r' % dmg_dst) | ||
213 | |||
214 | if (os.path.exists(final_dmg)): | ||
215 | bkp = final_dmg + ".bkp" | ||
216 | message("Renaming existing final disk image to %r..." % bkp) | ||
217 | shutil.move(final_dmg, bkp) | ||
218 | |||
219 | self.__run_command( | ||
220 | 'Creating compressed final disk image (this takes a while)...', | ||
221 | 'hdiutil convert %(temp)r -format UDBZ -o %(final)r' % | ||
222 | {'temp':temp_dmg, 'final':final_dmg}) | ||
223 | |||
224 | message("Removing temp disk image...") | ||
225 | os.remove(temp_dmg) | ||
226 | |||
227 | message('Package complete: %r'%final_dmg) | ||
228 | |||
229 | |||
230 | ########### | ||
231 | # WINDOWS # | ||
232 | ########### | ||
233 | |||
234 | def make_windows(self): | ||
235 | print "Packaging for Windows is not supported yet." | ||
236 | |||
237 | |||
238 | ################### | ||
239 | # PRIVATE METHODS # | ||
240 | ################### | ||
241 | |||
242 | def __check_build_dir(self): | ||
243 | if not os.path.exists(self.build_dir): | ||
244 | raise BadDir('build dir %(dir)r does not exist.' % | ||
245 | {'dir': self.build_dir}) | ||
246 | if not os.path.isdir(self.build_dir): | ||
247 | raise BadDir('build dir %(dir)r is not a directory.' % | ||
248 | {'dir': self.build_dir}) | ||
249 | |||
250 | def __check_source_dir(self): | ||
251 | if not os.path.exists(self.source_dir): | ||
252 | raise BadDir('source dir %(dir)r does not exist.' % | ||
253 | {'dir': self.source_dir}) | ||
254 | if not os.path.isdir(self.source_dir): | ||
255 | raise BadDir('source dir %(dir)r is not a directory.' % | ||
256 | {'dir': self.source_dir}) | ||
257 | |||
258 | def __get_platform(self): | ||
259 | platform = sys.platform | ||
260 | try: | ||
261 | return {'linux2':'linux', | ||
262 | 'linux1':'linux', | ||
263 | 'cygwin':'windows', | ||
264 | 'win32' :'windows', | ||
265 | 'darwin':'mac', | ||
266 | }[platform] | ||
267 | except KeyError: | ||
268 | raise WeirdPlatform( | ||
269 | "Unrecognized platform/operating system: %r" % platform) | ||
270 | |||
271 | def __run_command(self, summary=None, command=None): | ||
272 | if summary: message(summary) | ||
273 | |||
274 | if not command: return | ||
275 | |||
276 | import subprocess | ||
277 | |||
278 | out = subprocess.PIPE # = intercept command's output | ||
279 | if self.verbose: | ||
280 | print indent(command) | ||
281 | out = None # = don't intercept | ||
282 | |||
283 | child = subprocess.Popen(command, shell=True, stdout=out, stderr=None) | ||
284 | status = child.wait() | ||
285 | |||
286 | if status: | ||
287 | raise CmdFailed('A command returned non-zero status (%s):\n%s'% | ||
288 | (status, indent(command))) | ||
289 | |||
290 | |||
291 | |||
292 | def main(args=sys.argv[1:]): | ||
293 | from optparse import OptionParser | ||
294 | |||
295 | op = OptionParser(usage='%prog --build-dir=PATH [options]') | ||
296 | |||
297 | op.add_option('--build-dir', dest='build_dir', nargs=1, metavar='PATH', | ||
298 | help='path to the \'build\' directory, which contains ' | ||
299 | 'CMakeCache.txt and the compile result subdirectories') | ||
300 | |||
301 | op.add_option('--source-dir', dest='source_dir', nargs=1, metavar='PATH', | ||
302 | default=SOURCE_DIR, | ||
303 | help='optional path to an alternate source directory, ' | ||
304 | 'i.e. \'indra\'. Default: %(SOURCE_DIR)r' | ||
305 | %{ 'SOURCE_DIR': SOURCE_DIR } ) | ||
306 | |||
307 | op.add_option('--build-type', dest='build_type', nargs=1, metavar='TYPE', | ||
308 | default=BUILD_TYPE, | ||
309 | help='\'Debug\', \'RelWithDebInfo\', or \'Release\'. ' | ||
310 | 'Default: %(BUILD_TYPE)r' | ||
311 | %{ 'BUILD_TYPE': BUILD_TYPE } ) | ||
312 | |||
313 | op.add_option('-v', '--verbose', action='store_true', default=False, | ||
314 | help='print all shell commands as they are run') | ||
315 | |||
316 | if not args: | ||
317 | op.print_help() | ||
318 | return | ||
319 | |||
320 | options = op.parse_args(args)[0] | ||
321 | |||
322 | if not options.build_dir: | ||
323 | error('--build-dir=PATH is required.') | ||
324 | sys.exit(1) | ||
325 | |||
326 | opts_dict = {'source_dir': options.source_dir, | ||
327 | 'build_type': options.build_type, | ||
328 | 'verbose': options.verbose} | ||
329 | |||
330 | try: | ||
331 | Packager(options.build_dir, opts_dict).make() | ||
332 | except PackagerError, err: | ||
333 | error(err.args[0]) | ||
334 | sys.exit(1) | ||
335 | |||
336 | |||
337 | if __name__ == '__main__': | ||
338 | main() | ||