aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/scripts/package.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xlinden/scripts/package.py334
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
33import os, sys
34from viewer_info import ViewerInfo
35
36
37SCRIPTS_DIR = sys.path[0] # directory containing this script
38TOP_DIR = os.path.abspath(os.path.join(SCRIPTS_DIR,'..'))
39SOURCE_DIR = os.path.abspath(os.path.join(TOP_DIR,'indra'))
40BUILD_TYPE = "RelWithDebInfo"
41
42
43class PackagerError(Exception): pass
44
45class BadDir(PackagerError): pass
46
47class WeirdPlatform(PackagerError): pass
48
49class CmdFailed(PackagerError): pass
50
51
52def 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
57def message(*args):
58 """Prints an informational message with a leading '#'."""
59 print '# ' + ' '.join([str(arg) for arg in args])
60
61def error(*args):
62 """Prints an error message to stderr."""
63 print >> sys.stderr, 'Error: ' + ' '.join([str(arg) for arg in args])
64
65
66class 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;
112find %(d)r -type f -perm 0700 | xargs --no-run-if-empty chmod 0755;
113find %(d)r -type f -perm 0500 | xargs --no-run-if-empty chmod 0555;
114find %(d)r -type f -perm 0600 | xargs --no-run-if-empty chmod 0644;
115find %(d)r -type f -perm 0400 | xargs --no-run-if-empty chmod 0444;
116true""" % {'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
288def 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
333if __name__ == '__main__':
334 main()