diff options
Diffstat (limited to 'linden/scripts/package.py')
-rwxr-xr-x | linden/scripts/package.py | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/linden/scripts/package.py b/linden/scripts/package.py new file mode 100755 index 0000000..e02a9cc --- /dev/null +++ b/linden/scripts/package.py | |||
@@ -0,0 +1,334 @@ | |||
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 | 'Unmounting temp disk image...', | ||
208 | 'hdiutil detach %r' % dmg_dst) | ||
209 | |||
210 | if (os.path.exists(final_dmg)): | ||
211 | bkp = final_dmg + ".bkp" | ||
212 | message("Renaming existing final disk image to %r..." % bkp) | ||
213 | shutil.move(final_dmg, bkp) | ||
214 | |||
215 | self.__run_command( | ||
216 | 'Creating compressed final disk image (this takes a while)...', | ||
217 | 'hdiutil convert %(temp)r -format UDBZ -o %(final)r' % | ||
218 | {'temp':temp_dmg, 'final':final_dmg}) | ||
219 | |||
220 | message("Removing temp disk image...") | ||
221 | os.remove(temp_dmg) | ||
222 | |||
223 | message('Package complete: %r'%final_dmg) | ||
224 | |||
225 | |||
226 | ########### | ||
227 | # WINDOWS # | ||
228 | ########### | ||
229 | |||
230 | def make_windows(self): | ||
231 | print "Packaging for Windows is not supported yet." | ||
232 | |||
233 | |||
234 | ################### | ||
235 | # PRIVATE METHODS # | ||
236 | ################### | ||
237 | |||
238 | def __check_build_dir(self): | ||
239 | if not os.path.exists(self.build_dir): | ||
240 | raise BadDir('build dir %(dir)r does not exist.' % | ||
241 | {'dir': self.build_dir}) | ||
242 | if not os.path.isdir(self.build_dir): | ||
243 | raise BadDir('build dir %(dir)r is not a directory.' % | ||
244 | {'dir': self.build_dir}) | ||
245 | |||
246 | def __check_source_dir(self): | ||
247 | if not os.path.exists(self.source_dir): | ||
248 | raise BadDir('source dir %(dir)r does not exist.' % | ||
249 | {'dir': self.source_dir}) | ||
250 | if not os.path.isdir(self.source_dir): | ||
251 | raise BadDir('source dir %(dir)r is not a directory.' % | ||
252 | {'dir': self.source_dir}) | ||
253 | |||
254 | def __get_platform(self): | ||
255 | platform = sys.platform | ||
256 | try: | ||
257 | return {'linux2':'linux', | ||
258 | 'linux1':'linux', | ||
259 | 'cygwin':'windows', | ||
260 | 'win32' :'windows', | ||
261 | 'darwin':'mac', | ||
262 | }[platform] | ||
263 | except KeyError: | ||
264 | raise WeirdPlatform( | ||
265 | "Unrecognized platform/operating system: %r" % platform) | ||
266 | |||
267 | def __run_command(self, summary=None, command=None): | ||
268 | if summary: message(summary) | ||
269 | |||
270 | if not command: return | ||
271 | |||
272 | import subprocess | ||
273 | |||
274 | out = subprocess.PIPE # = intercept command's output | ||
275 | if self.verbose: | ||
276 | print indent(command) | ||
277 | out = None # = don't intercept | ||
278 | |||
279 | child = subprocess.Popen(command, shell=True, stdout=out, stderr=None) | ||
280 | status = child.wait() | ||
281 | |||
282 | if status: | ||
283 | raise CmdFailed('A command returned non-zero status (%s):\n%s'% | ||
284 | (status, indent(command))) | ||
285 | |||
286 | |||
287 | |||
288 | def main(args=sys.argv[1:]): | ||
289 | from optparse import OptionParser | ||
290 | |||
291 | op = OptionParser(usage='%prog --build-dir=PATH [options]') | ||
292 | |||
293 | op.add_option('--build-dir', dest='build_dir', nargs=1, metavar='PATH', | ||
294 | help='path to the \'build\' directory, which contains ' | ||
295 | 'CMakeCache.txt and the compile result subdirectories') | ||
296 | |||
297 | op.add_option('--source-dir', dest='source_dir', nargs=1, metavar='PATH', | ||
298 | default=SOURCE_DIR, | ||
299 | help='optional path to an alternate source directory, ' | ||
300 | 'i.e. \'indra\'. Default: %(SOURCE_DIR)r' | ||
301 | %{ 'SOURCE_DIR': SOURCE_DIR } ) | ||
302 | |||
303 | op.add_option('--build-type', dest='build_type', nargs=1, metavar='TYPE', | ||
304 | default=BUILD_TYPE, | ||
305 | help='\'Debug\', \'RelWithDebInfo\', or \'Release\'. ' | ||
306 | 'Default: %(BUILD_TYPE)r' | ||
307 | %{ 'BUILD_TYPE': BUILD_TYPE } ) | ||
308 | |||
309 | op.add_option('-v', '--verbose', action='store_true', default=False, | ||
310 | help='print all shell commands as they are run') | ||
311 | |||
312 | if not args: | ||
313 | op.print_help() | ||
314 | return | ||
315 | |||
316 | options = op.parse_args(args)[0] | ||
317 | |||
318 | if not options.build_dir: | ||
319 | error('--build-dir=PATH is required.') | ||
320 | sys.exit(1) | ||
321 | |||
322 | opts_dict = {'source_dir': options.source_dir, | ||
323 | 'build_type': options.build_type, | ||
324 | 'verbose': options.verbose} | ||
325 | |||
326 | try: | ||
327 | Packager(options.build_dir, opts_dict).make() | ||
328 | except PackagerError, err: | ||
329 | error(err.args[0]) | ||
330 | sys.exit(1) | ||
331 | |||
332 | |||
333 | if __name__ == '__main__': | ||
334 | main() | ||