aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/scripts/package.py
diff options
context:
space:
mode:
authorJacek Antonelli2011-05-20 20:37:38 -0500
committerJacek Antonelli2011-05-20 22:09:31 -0500
commit13b3170509117b6e00e612137afab1ec7a543cd3 (patch)
tree514adb46ff6a783059a579ab987f7eb83385ce4b /linden/scripts/package.py
parentPorted viewer_info.py and BuildVersion.cmake from Kokua. (diff)
downloadmeta-impy-13b3170509117b6e00e612137afab1ec7a543cd3.zip
meta-impy-13b3170509117b6e00e612137afab1ec7a543cd3.tar.gz
meta-impy-13b3170509117b6e00e612137afab1ec7a543cd3.tar.bz2
meta-impy-13b3170509117b6e00e612137afab1ec7a543cd3.tar.xz
Ported Linux packaging system from Kokua.
Run "make package" in the build directory to create a tarball.
Diffstat (limited to 'linden/scripts/package.py')
-rwxr-xr-xlinden/scripts/package.py338
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
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 # '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
292def 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
337if __name__ == '__main__':
338 main()