aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/viewer_manifest.py
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/newview/viewer_manifest.py')
-rwxr-xr-xlinden/indra/newview/viewer_manifest.py1088
1 files changed, 595 insertions, 493 deletions
diff --git a/linden/indra/newview/viewer_manifest.py b/linden/indra/newview/viewer_manifest.py
index ae25745..fe894a7 100755
--- a/linden/indra/newview/viewer_manifest.py
+++ b/linden/indra/newview/viewer_manifest.py
@@ -39,518 +39,620 @@ sys.path.append(os.path.join(viewer_dir, '../lib/python/indra/util'))
39from llmanifest import LLManifest, main, proper_windows_path, path_ancestors 39from llmanifest import LLManifest, main, proper_windows_path, path_ancestors
40 40
41class ViewerManifest(LLManifest): 41class ViewerManifest(LLManifest):
42 def construct(self): 42 def construct(self):
43 super(ViewerManifest, self).construct() 43 super(ViewerManifest, self).construct()
44 self.exclude("*.svn*") 44 self.exclude("*.svn*")
45 self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg") 45 self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg")
46 self.path(src="../../etc/message.xml", dst="app_settings/message.xml") 46 self.path(src="../../etc/message.xml", dst="app_settings/message.xml")
47 47
48 if self.prefix(src="app_settings"): 48 if self.prefix(src="app_settings"):
49 self.exclude("logcontrol.xml") 49 self.exclude("logcontrol.xml")
50 self.exclude("logcontrol-dev.xml") 50 self.exclude("logcontrol-dev.xml")
51 self.path("*.pem") 51 self.path("*.pem")
52 self.path("*.ini") 52 self.path("*.ini")
53 self.path("*.xml") 53 self.path("*.xml")
54 self.path("*.vp") 54 self.path("*.vp")
55 self.path("*.db2") 55 self.path("*.db2")
56 56
57 # include the entire shaders directory recursively 57 # include the entire shaders directory recursively
58 self.path("shaders") 58 self.path("shaders")
59 # ... and the entire windlight directory 59 # ... and the entire windlight directory
60 self.path("windlight") 60 self.path("windlight")
61 self.end_prefix("app_settings") 61 self.end_prefix("app_settings")
62 62
63 if self.prefix(src="character"): 63 if self.prefix(src="character"):
64 self.path("*.llm") 64 self.path("*.llm")
65 self.path("*.xml") 65 self.path("*.xml")
66 self.path("*.tga") 66 self.path("*.tga")
67 self.end_prefix("character") 67 self.end_prefix("character")
68 68
69 69
70 # Include our fonts 70 # Include our fonts
71 if self.prefix(src="fonts"): 71 if self.prefix(src="fonts"):
72 self.path("*.ttf") 72 self.path("*.ttf")
73 self.path("*.txt") 73 self.path("*.txt")
74 self.end_prefix("fonts") 74 self.end_prefix("fonts")
75 75
76 # skins 76 # skins
77 if self.prefix(src="skins"): 77 if self.prefix(src="skins"):
78 self.path("paths.xml") 78 self.path("paths.xml")
79 # include the entire textures directory recursively 79 # include the entire textures directory recursively
80 if self.prefix(src="*/textures"): 80 if self.prefix(src="*/textures"):
81 self.path("*.tga") 81 self.path("*.tga")
82 self.path("*.j2c") 82 self.path("*.j2c")
83 self.path("*.jpg") 83 self.path("*.jpg")
84 self.path("*.png") 84 self.path("*.png")
85 self.path("textures.xml") 85 self.path("textures.xml")
86 self.end_prefix("*/textures") 86 self.end_prefix("*/textures")
87 self.path("*/xui/*/*.xml") 87 self.path("*/xui/*/*.xml")
88 self.path("*/*.xml") 88 self.path("*/*.xml")
89
90 # Local HTML files (e.g. loading screen)
91 if self.prefix(src="*/html"):
92 self.path("*.png")
93 self.path("*/*/*.html")
94 self.path("*/*/*.gif")
95 self.end_prefix("*/html")
96 self.end_prefix("skins")
97 self.path("lsl_guide.html")
98 self.path("gpu_table.txt")
99
100 def login_channel(self):
101 """Channel reported for login and upgrade purposes ONLY;
102 used for A/B testing"""
103 # NOTE: Do not return the normal channel if login_channel
104 # is not specified, as some code may branch depending on
105 # whether or not this is present
106 return self.args.get('login_channel')
107
108 def channel(self):
109 return self.args['channel']
110 def channel_unique(self):
111 return self.channel().replace("Second Life", "").strip()
112 def channel_oneword(self):
113 return "".join(self.channel_unique().split())
114 def channel_lowerword(self):
115 return self.channel_oneword().lower()
116
117 def flags_list(self):
118 """ Convenience function that returns the command-line flags
119 for the grid"""
120 channel_flags = ''
121 grid_flags = ''
122 if not self.default_grid():
123 if self.default_channel():
124 # beta grid viewer
125 channel_flags = '--settings settings_beta.xml'
126 grid_flags = "--grid %(grid)s --helperuri http://preview-%(grid)s.secondlife.com/helpers/" % {'grid':self.args['grid']}
127
128 if not self.default_channel():
129 # some channel on some grid
130 channel_flags = '--settings settings_%s.xml --channel "%s"' % (self.channel_lowerword(), self.channel())
131 elif self.login_channel():
132 # Report a special channel during login, but use default
133 channel_flags = '--channel "%s"' % (self.login_channel())
89 134
90 # Local HTML files (e.g. loading screen) 135 return " ".join((channel_flags, grid_flags)).strip()
91 if self.prefix(src="*/html"):
92 self.path("*.png")
93 self.path("*/*/*.html")
94 self.path("*/*/*.gif")
95 self.end_prefix("*/html")
96 self.end_prefix("skins")
97 self.path("releasenotes.txt")
98 self.path("lsl_guide.html")
99 self.path("gpu_table.txt")
100
101 def login_channel(self):
102 """Channel reported for login and upgrade purposes ONLY; used for A/B testing"""
103 # NOTE: Do not return the normal channel if login_channel is not specified, as
104 # some code may branch depending on whether or not this is present
105 return self.args.get('login_channel')
106
107 def channel(self):
108 return self.args['channel']
109 def channel_unique(self):
110 return self.channel().replace("Second Life", "").strip()
111 def channel_oneword(self):
112 return "".join(self.channel_unique().split())
113 def channel_lowerword(self):
114 return self.channel_oneword().lower()
115
116 def flags_list(self):
117 """ Convenience function that returns the command-line flags for the grid"""
118 channel_flags = ''
119 grid_flags = ''
120 if not self.default_grid():
121 if self.default_channel():
122 # beta grid viewer
123 channel_flags = '--settings settings_beta.xml'
124 grid_flags = "--grid %(grid)s --helperuri http://preview-%(grid)s.secondlife.com/helpers/" % {'grid':self.args['grid']}
125
126 if not self.default_channel():
127 # some channel on some grid
128 channel_flags = '--settings settings_%s.xml --channel "%s"' % (self.channel_lowerword(), self.channel())
129 elif self.login_channel():
130 # Report a special channel during login, but use default channel elsewhere
131 channel_flags = '--channel "%s"' % (self.login_channel())
132
133 return " ".join((channel_flags, grid_flags)).strip()
134 136
135 137
136class WindowsManifest(ViewerManifest): 138class WindowsManifest(ViewerManifest):
137 def final_exe(self): 139 def final_exe(self):
138 if self.default_channel(): 140 if self.default_channel():
139 if self.default_grid(): 141 if self.default_grid():
140 return "SecondLife.exe" 142 return "SecondLife.exe"
141 else: 143 else:
142 return "SecondLifePreview.exe" 144 return "SecondLifePreview.exe"
143 else: 145 else:
144 return ''.join(self.channel().split()) + '.exe' 146 return ''.join(self.channel().split()) + '.exe'
145 147
146 148
147 def construct(self): 149 def construct(self):
148 super(WindowsManifest, self).construct() 150 super(WindowsManifest, self).construct()
149 # the final exe is complicated because we're not sure where it's coming from, 151 # the final exe is complicated because we're not sure where it's coming from,
150 # nor do we have a fixed name for the executable 152 # nor do we have a fixed name for the executable
151 self.path(self.find_existing_file('ReleaseForDownload/Secondlife.exe', 'Secondlife.exe', 'ReleaseNoOpt/newview_noopt.exe'), dst=self.final_exe()) 153 self.path(self.find_existing_file('debug/secondlife-bin.exe', 'release/secondlife-bin.exe', 'relwithdebinfo/secondlife-bin.exe'), dst=self.final_exe())
152 # need to get the kdu dll from any of the build directories as well 154 # need to get the kdu dll from any of the build directories as well
153 self.path(self.find_existing_file('ReleaseForDownload/llkdu.dll', 'llkdu.dll', '../../libraries/i686-win32/lib_release/llkdu.dll'), dst='llkdu.dll') 155 self.path(self.find_existing_file(
154 self.path(src="licenses-win32.txt", dst="licenses.txt") 156 # *FIX:Mani we need to add support for packaging specific targets.
155 157 #'../llkdu/debug/llkdu.dll',
156 self.path("featuretable.txt") 158 '../llkdu/release/llkdu.dll',
157 159 '../llkdu/relwithdebinfo/llkdu.dll',
158 # For use in crash reporting (generates minidumps) 160 '../../libraries/i686-win32/lib/release/llkdu.dll'),
159 self.path("dbghelp.dll") 161 dst='llkdu.dll')
160 162 self.path(src="licenses-win32.txt", dst="licenses.txt")
161 # For using FMOD for sound... DJS 163
162 self.path("fmod.dll") 164 self.path("featuretable.txt")
163 165
164 # For textures 166 # For use in crash reporting (generates minidumps)
165 if self.prefix(src="../../libraries/i686-win32/lib_release", dst=""): 167 self.path("dbghelp.dll")
166 self.path("openjpeg.dll") 168
167 self.end_prefix() 169 # For using FMOD for sound... DJS
168 170 self.path("fmod.dll")
169 # Mozilla appears to force a dependency on these files so we need to ship it (CP) 171
170 self.path("msvcr71.dll") 172 # For textures
171 self.path("msvcp71.dll") 173 if self.prefix(src="../../libraries/i686-win32/lib/release", dst=""):
172 174 self.path("openjpeg.dll")
173 # Mozilla runtime DLLs (CP) 175 self.end_prefix()
174 if self.prefix(src="../../libraries/i686-win32/lib_release", dst=""): 176
175 self.path("freebl3.dll") 177 # Mozilla appears to force a dependency on these files so we need to ship it (CP)
176 self.path("gksvggdiplus.dll") 178 self.path("msvcr71.dll")
177 self.path("js3250.dll") 179 self.path("msvcp71.dll")
178 self.path("nspr4.dll") 180
179 self.path("nss3.dll") 181 # Mozilla runtime DLLs (CP)
180 self.path("nssckbi.dll") 182 if self.prefix(src="../../libraries/i686-win32/lib/release", dst=""):
181 self.path("plc4.dll") 183 self.path("freebl3.dll")
182 self.path("plds4.dll") 184 self.path("gksvggdiplus.dll")
183 self.path("smime3.dll") 185 self.path("js3250.dll")
184 self.path("softokn3.dll") 186 self.path("nspr4.dll")
185 self.path("ssl3.dll") 187 self.path("nss3.dll")
186 self.path("xpcom.dll") 188 self.path("nssckbi.dll")
187 self.path("xul.dll") 189 self.path("plc4.dll")
188 self.end_prefix() 190 self.path("plds4.dll")
189 191 self.path("smime3.dll")
190 # Mozilla runtime misc files (CP) 192 self.path("softokn3.dll")
191 if self.prefix(src="app_settings/mozilla"): 193 self.path("ssl3.dll")
192 self.path("chrome/*.*") 194 self.path("xpcom.dll")
193 self.path("components/*.*") 195 self.path("xul.dll")
194 self.path("greprefs/*.*") 196 self.end_prefix()
195 self.path("plugins/*.*") 197
196 self.path("res/*.*") 198 # Mozilla runtime misc files (CP)
197 self.path("res/*/*") 199 if self.prefix(src="app_settings/mozilla"):
198 self.end_prefix() 200 self.path("chrome/*.*")
199 201 self.path("components/*.*")
200 # Vivox runtimes 202 self.path("greprefs/*.*")
201 if self.prefix(src="vivox-runtime/i686-win32", dst=""): 203 self.path("plugins/*.*")
202 self.path("SLVoice.exe") 204 self.path("res/*.*")
203 self.path("SLVoiceAgent.exe") 205 self.path("res/*/*")
204 self.path("libeay32.dll") 206 self.end_prefix()
205 self.path("srtp.dll") 207
206 self.path("ssleay32.dll") 208 # Vivox runtimes
207 self.path("tntk.dll") 209 if self.prefix(src="vivox-runtime/i686-win32", dst=""):
208 self.path("alut.dll") 210 self.path("SLVoice.exe")
209 self.path("vivoxsdk.dll") 211 self.path("SLVoiceAgent.exe")
210 self.path("ortp.dll") 212 self.path("libeay32.dll")
211 self.path("wrap_oal.dll") 213 self.path("srtp.dll")
212 self.end_prefix() 214 self.path("ssleay32.dll")
213 215 self.path("tntk.dll")
214# # pull in the crash logger and updater from other projects 216 self.path("alut.dll")
215# self.path(src="../win_crash_logger/win_crash_logger.exe", dst="win_crash_logger.exe") 217 self.path("vivoxsdk.dll")
216 self.path(src="../win_updater/updater.exe", dst="updater.exe") 218 self.path("ortp.dll")
217 219 self.path("wrap_oal.dll")
218 def nsi_file_commands(self, install=True): 220 self.end_prefix()
219 def wpath(path): 221
220 if(path.endswith('/') or path.endswith(os.path.sep)): 222# # pull in the crash logger and updater from other projects
221 path = path[:-1] 223# self.path(src=self.find_existing_file( # tag:"crash-logger" here as a cue to the exporter
222 path = path.replace('/', '\\') 224# "../win_crash_logger/debug/windows-crash-logger.exe",
223 return path 225# "../win_crash_logger/release/windows-crash-logger.exe",
224 226# "../win_crash_logger/relwithdebinfo/windows-crash-logger.exe"),
225 result = "" 227# dst="win_crash_logger.exe")
226 dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])] 228 self.path(src=self.find_existing_file(
227 # sort deepest hierarchy first 229 "../win_updater/debug/windows-updater.exe",
228 dest_files.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b)) 230 "../win_updater/release/windows-updater.exe",
229 dest_files.reverse() 231 "../win_updater/relwithdebinfo/windows-updater.exe"),
230 out_path = None 232 dst="updater.exe")
231 for pkg_file in dest_files: 233
232 rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,'')) 234 def nsi_file_commands(self, install=True):
233 installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file))) 235 def wpath(path):
234 pkg_file = wpath(os.path.normpath(pkg_file)) 236 if path.endswith('/') or path.endswith(os.path.sep):
235 if installed_dir != out_path: 237 path = path[:-1]
236 if(install): 238 path = path.replace('/', '\\')
237 out_path = installed_dir 239 return path
238 result += 'SetOutPath ' + out_path + '\n' 240
239 if(install): 241 result = ""
240 result += 'File ' + pkg_file + '\n' 242 dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])]
241 else: 243 # sort deepest hierarchy first
242 result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n' 244 dest_files.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
243 # at the end of a delete, just rmdir all the directories 245 dest_files.reverse()
244 if(not install): 246 out_path = None
245 deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list] 247 for pkg_file in dest_files:
246 # find all ancestors so that we don't skip any dirs that happened to have no non-dir children 248 rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,''))
247 deleted_dirs = [] 249 installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file)))
248 for d in deleted_file_dirs: 250 pkg_file = wpath(os.path.normpath(pkg_file))
249 deleted_dirs.extend(path_ancestors(d)) 251 if installed_dir != out_path:
250 # sort deepest hierarchy first 252 if install:
251 deleted_dirs.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b)) 253 out_path = installed_dir
252 deleted_dirs.reverse() 254 result += 'SetOutPath ' + out_path + '\n'
253 prev = None 255 if install:
254 for d in deleted_dirs: 256 result += 'File ' + pkg_file + '\n'
255 if d != prev: # skip duplicates 257 else:
256 result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n' 258 result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n'
257 prev = d 259 # at the end of a delete, just rmdir all the directories
258 260 if not install:
259 return result 261 deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list]
260 262 # find all ancestors so that we don't skip any dirs that happened to have no non-dir children
261 def package_finish(self): 263 deleted_dirs = []
262 # a standard map of strings for replacing in the templates 264 for d in deleted_file_dirs:
263 substitution_strings = { 265 deleted_dirs.extend(path_ancestors(d))
264 'version' : '.'.join(self.args['version']), 266 # sort deepest hierarchy first
265 'version_short' : '.'.join(self.args['version'][:-1]), 267 deleted_dirs.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
266 'version_dashes' : '-'.join(self.args['version']), 268 deleted_dirs.reverse()
267 'final_exe' : self.final_exe(), 269 prev = None
268 'grid':self.args['grid'], 270 for d in deleted_dirs:
269 'grid_caps':self.args['grid'].upper(), 271 if d != prev: # skip duplicates
270 # escape quotes becase NSIS doesn't handle them well 272 result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n'
271 'flags':self.flags_list().replace('"', '$\\"'), 273 prev = d
272 'channel':self.channel(), 274
273 'channel_oneword':self.channel_oneword(), 275 return result
274 'channel_unique':self.channel_unique(), 276
275 } 277 def package_finish(self):
276 278 # a standard map of strings for replacing in the templates
277 version_vars = """ 279 substitution_strings = {
278 !define INSTEXE "%(final_exe)s" 280 'version' : '.'.join(self.args['version']),
279 !define VERSION "%(version_short)s" 281 'version_short' : '.'.join(self.args['version'][:-1]),
280 !define VERSION_LONG "%(version)s" 282 'version_dashes' : '-'.join(self.args['version']),
281 !define VERSION_DASHES "%(version_dashes)s" 283 'final_exe' : self.final_exe(),
282 """ % substitution_strings 284 'grid':self.args['grid'],
283 if self.default_channel(): 285 'grid_caps':self.args['grid'].upper(),
284 if self.default_grid(): 286 # escape quotes becase NSIS doesn't handle them well
285 # release viewer 287 'flags':self.flags_list().replace('"', '$\\"'),
286 installer_file = "Second_Life_%(version_dashes)s_Setup.exe" 288 'channel':self.channel(),
287 grid_vars_template = """ 289 'channel_oneword':self.channel_oneword(),
288 OutFile "%(installer_file)s" 290 'channel_unique':self.channel_unique(),
289 !define INSTFLAGS "%(flags)s" 291 }
290 !define INSTNAME "SecondLife" 292
291 !define SHORTCUT "Second Life" 293 version_vars = """
292 !define URLNAME "secondlife" 294 !define INSTEXE "%(final_exe)s"
293 Caption "Second Life ${VERSION}" 295 !define VERSION "%(version_short)s"
294 """ 296 !define VERSION_LONG "%(version)s"
295 else: 297 !define VERSION_DASHES "%(version_dashes)s"
296 # beta grid viewer 298 """ % substitution_strings
297 installer_file = "Second_Life_%(version_dashes)s_(%(grid_caps)s)_Setup.exe" 299 if self.default_channel():
298 grid_vars_template = """ 300 if self.default_grid():
299 OutFile "%(installer_file)s" 301 # release viewer
300 !define INSTFLAGS "%(flags)s" 302 installer_file = "Second_Life_%(version_dashes)s_Setup.exe"
301 !define INSTNAME "SecondLife%(grid_caps)s" 303 grid_vars_template = """
302 !define SHORTCUT "Second Life (%(grid_caps)s)" 304 OutFile "%(installer_file)s"
303 !define URLNAME "secondlife%(grid)s" 305 !define INSTFLAGS "%(flags)s"
304 !define UNINSTALL_SETTINGS 1 306 !define INSTNAME "SecondLife"
305 Caption "Second Life %(grid)s ${VERSION}" 307 !define SHORTCUT "Second Life"
306 """ 308 !define URLNAME "secondlife"
307 else: 309 Caption "Second Life ${VERSION}"
308 # some other channel on some grid 310 """
309 installer_file = "Second_Life_%(version_dashes)s_%(channel_oneword)s_Setup.exe" 311 else:
310 grid_vars_template = """ 312 # beta grid viewer
311 OutFile "%(installer_file)s" 313 installer_file = "Second_Life_%(version_dashes)s_(%(grid_caps)s)_Setup.exe"
312 !define INSTFLAGS "%(flags)s" 314 grid_vars_template = """
313 !define INSTNAME "SecondLife%(channel_oneword)s" 315 OutFile "%(installer_file)s"
314 !define SHORTCUT "%(channel)s" 316 !define INSTFLAGS "%(flags)s"
315 !define URLNAME "secondlife" 317 !define INSTNAME "SecondLife%(grid_caps)s"
316 !define UNINSTALL_SETTINGS 1 318 !define SHORTCUT "Second Life (%(grid_caps)s)"
317 Caption "%(channel)s ${VERSION}" 319 !define URLNAME "secondlife%(grid)s"
318 """ 320 !define UNINSTALL_SETTINGS 1
319 if(self.args.has_key('installer_name')): 321 Caption "Second Life %(grid)s ${VERSION}"
320 installer_file = self.args['installer_name'] 322 """
321 else: 323 else:
322 installer_file = installer_file % substitution_strings 324 # some other channel on some grid
323 substitution_strings['installer_file'] = installer_file 325 installer_file = "Second_Life_%(version_dashes)s_%(channel_oneword)s_Setup.exe"
324 326 grid_vars_template = """
325 tempfile = "../secondlife_setup_tmp.nsi" 327 OutFile "%(installer_file)s"
326 # the following replaces strings in the nsi template 328 !define INSTFLAGS "%(flags)s"
327 # it also does python-style % substitution 329 !define INSTNAME "SecondLife%(channel_oneword)s"
328 self.replace_in("installers/windows/installer_template.nsi", tempfile, { 330 !define SHORTCUT "%(channel)s"
329 "%%VERSION%%":version_vars, 331 !define URLNAME "secondlife"
330 "%%GRID_VARS%%":grid_vars_template % substitution_strings, 332 !define UNINSTALL_SETTINGS 1
331 "%%INSTALL_FILES%%":self.nsi_file_commands(True), 333 Caption "%(channel)s ${VERSION}"
332 "%%DELETE_FILES%%":self.nsi_file_commands(False)}) 334 """
333 335 if 'installer_name' in self.args:
334 NSIS_path = 'C:\\Program Files\\NSIS\\makensis.exe' 336 installer_file = self.args['installer_name']
335 self.run_command('"' + proper_windows_path(NSIS_path) + '" ' + self.dst_path_of(tempfile)) 337 else:
336 # self.remove(self.dst_path_of(tempfile)) 338 installer_file = installer_file % substitution_strings
337 self.created_path(installer_file) 339 substitution_strings['installer_file'] = installer_file
340
341 tempfile = "secondlife_setup_tmp.nsi"
342 # the following replaces strings in the nsi template
343 # it also does python-style % substitution
344 self.replace_in("installers/windows/installer_template.nsi", tempfile, {
345 "%%VERSION%%":version_vars,
346 "%%SOURCE%%":self.get_src_prefix(),
347 "%%GRID_VARS%%":grid_vars_template % substitution_strings,
348 "%%INSTALL_FILES%%":self.nsi_file_commands(True),
349 "%%DELETE_FILES%%":self.nsi_file_commands(False)})
350
351 NSIS_path = 'C:\\Program Files\\NSIS\\makensis.exe'
352 self.run_command('"' + proper_windows_path(NSIS_path) + '" ' + self.dst_path_of(tempfile))
353 # self.remove(self.dst_path_of(tempfile))
354 self.created_path(self.dst_path_of(installer_file))
355 self.package_file = installer_file
338 356
339 357
340class DarwinManifest(ViewerManifest): 358class DarwinManifest(ViewerManifest):
341 def construct(self): 359 def construct(self):
342 # copy over the build result (this is a no-op if run within the xcode script) 360 # copy over the build result (this is a no-op if run within the xcode script)
343 self.path("build/" + self.args['configuration'] + "/Second Life.app", dst="") 361 self.path(self.args['configuration'] + "/Second Life.app", dst="")
344 362
345 if self.prefix(src="", dst="Contents"): # everything goes in Contents 363 if self.prefix(src="", dst="Contents"): # everything goes in Contents
346 # Expand the tar file containing the assorted mozilla bits into 364 # Expand the tar file containing the assorted mozilla bits into
347 # <bundle>/Contents/MacOS/ 365 # <bundle>/Contents/MacOS/
348 self.contents_of_tar('mozilla-universal-darwin.tgz', 'MacOS') 366 self.contents_of_tar(self.args['source']+'/mozilla-universal-darwin.tgz', 'MacOS')
349 367
350 # copy additional libs in <bundle>/Contents/MacOS/ 368 self.path("Info-SecondLife.plist", dst="Info.plist")
351 self.path("../../libraries/universal-darwin/lib_release/libndofdev.dylib", dst="MacOS/libndofdev.dylib") 369
352 370 # copy additional libs in <bundle>/Contents/MacOS/
353 # replace the default theme with our custom theme (so scrollbars work). 371 self.path("../../libraries/universal-darwin/lib_release/libndofdev.dylib", dst="MacOS/libndofdev.dylib")
354 if self.prefix(src="mozilla-theme", dst="MacOS/chrome"): 372
355 self.path("classic.jar") 373 # replace the default theme with our custom theme (so scrollbars work).
356 self.path("classic.manifest") 374 if self.prefix(src="mozilla-theme", dst="MacOS/chrome"):
357 self.end_prefix("MacOS/chrome") 375 self.path("classic.jar")
358 376 self.path("classic.manifest")
359 # most everything goes in the Resources directory 377 self.end_prefix("MacOS/chrome")
360 if self.prefix(src="", dst="Resources"): 378
361 super(DarwinManifest, self).construct() 379 # most everything goes in the Resources directory
362 380 if self.prefix(src="", dst="Resources"):
363 if self.prefix("cursors_mac"): 381 super(DarwinManifest, self).construct()
364 self.path("*.tif") 382
365 self.end_prefix("cursors_mac") 383 if self.prefix("cursors_mac"):
366 384 self.path("*.tif")
367 self.path("licenses-mac.txt", dst="licenses.txt") 385 self.end_prefix("cursors_mac")
368 self.path("featuretable_mac.txt") 386
369 self.path("secondlife.icns") 387 self.path("licenses-mac.txt", dst="licenses.txt")
370 388 self.path("featuretable_mac.txt")
371 # llkdu dynamic library 389 self.path("SecondLife.nib")
372# self.path("../../libraries/universal-darwin/lib_release/libllkdu.dylib", "libllkdu.dylib") 390
373 391 # If we are not using the default channel, use the 'Firstlook
374 # command line arguments for connecting to the proper grid 392 # icon' to show that it isn't a stable release.
375 self.put_in_file(self.flags_list(), 'arguments.txt') 393 if self.default_channel() and self.default_grid():
376 394 self.path("secondlife.icns")
377 self.end_prefix("Resources")
378
379 self.end_prefix("Contents")
380
381 # NOTE: the -S argument to strip causes it to keep enough info for
382 # annotated backtraces (i.e. function names in the crash log). 'strip' with no
383 # arguments yields a slightly smaller binary but makes crash logs mostly useless.
384 # This may be desirable for the final release. Or not.
385 if("package" in self.args['actions'] or
386 "unpacked" in self.args['actions']):
387 self.run_command('strip -S "%(viewer_binary)s"' %
388 { 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
389
390
391 def package_finish(self):
392 channel_standin = 'Second Life' # hah, our default channel is not usable on its own
393 if not self.default_channel():
394 channel_standin = self.channel()
395
396 imagename="SecondLife_" + '_'.join(self.args['version'])
397 if self.default_channel():
398 if not self.default_grid():
399 # beta case
400 imagename = imagename + '_' + self.args['grid'].upper()
401 else:
402 # first look, etc
403 imagename = imagename + '_' + self.channel_oneword().upper()
404
405 sparsename = imagename + ".sparseimage"
406 finalname = imagename + ".dmg"
407 # make sure we don't have stale files laying about
408 self.remove(sparsename, finalname)
409
410 self.run_command('hdiutil create "%(sparse)s" -volname "%(channel)s" -fs HFS+ -type SPARSE -megabytes 300 -layout SPUD' % {
411 'sparse':sparsename,
412 'channel':channel_standin})
413
414 # mount the image and get the name of the mount point and device node
415 hdi_output = self.run_command('hdiutil attach -private "' + sparsename + '"')
416 devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
417 volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
418
419 # Copy everything in to the mounted .dmg
420 if self.default_channel() and not self.default_grid():
421 app_name = "Second Life " + self.args['grid']
422 else: 395 else:
423 app_name = channel_standin.strip() 396 self.path("secondlife_firstlook.icns", "secondlife.icns")
424 397
425 for s,d in {self.get_dst_prefix():app_name + ".app", 398 # Translations
426 "lsl_guide.html":"Linden Scripting Language Guide.html", 399 self.path("English.lproj")
427 "releasenotes.txt":"Release Notes.txt", 400 self.path("German.lproj")
428 "installers/darwin/mac_image_hidden":".hidden", 401 self.path("Japanese.lproj")
429 "installers/darwin/mac_image_background.tga":"background.tga", 402 self.path("Korean.lproj")
430 "installers/darwin/mac_image_DS_Store":".DS_Store"}.items(): 403
431 print "Copying to dmg", s, d 404 # SLVoice and vivox lols
432 self.copy_action(self.src_path_of(s), os.path.join(volpath, d)) 405 self.path("vivox-runtime/universal-darwin/libalut.dylib", "libalut.dylib")
433 406 self.path("vivox-runtime/universal-darwin/libopenal.dylib", "libopenal.dylib")
434 # Unmount the image 407 self.path("vivox-runtime/universal-darwin/libortp.dylib", "libortp.dylib")
435 self.run_command('hdiutil detach -force "' + devfile + '"') 408 self.path("vivox-runtime/universal-darwin/libvivoxsdk.dylib", "libvivoxsdk.dylib")
436 409 self.path("vivox-runtime/universal-darwin/SLVoice", "SLVoice")
437 print "Converting temp disk image to final disk image" 410 self.path("vivox-runtime/universal-darwin/SLVoiceAgent.app", "SLVoiceAgent.app")
438 self.run_command('hdiutil convert "%(sparse)s" -format UDZO -imagekey zlib-level=9 -o "%(final)s"' % {'sparse':sparsename, 'final':finalname}) 411
439 # get rid of the temp file 412 # llkdu dynamic library
440 self.remove(sparsename) 413# self.path("../../libraries/universal-darwin/lib_release/libllkdu.dylib", "libllkdu.dylib")
414
415 #libfmodwrapper.dylib
416 self.path(self.args['configuration'] + "/libfmodwrapper.dylib", "libfmodwrapper.dylib")
417
418 # our apps
419# self.path("../mac_crash_logger/" + self.args['configuration'] + "/mac-crash-logger.app", "mac-crash-logger.app")
420 self.path("../mac_updater/" + self.args['configuration'] + "/mac-updater.app", "mac-updater.app")
421
422 # command line arguments for connecting to the proper grid
423 self.put_in_file(self.flags_list(), 'arguments.txt')
424
425 self.end_prefix("Resources")
426
427 self.end_prefix("Contents")
428
429 # NOTE: the -S argument to strip causes it to keep enough info for
430 # annotated backtraces (i.e. function names in the crash log). 'strip' with no
431 # arguments yields a slightly smaller binary but makes crash logs mostly useless.
432 # This may be desirable for the final release. Or not.
433 if ("package" in self.args['actions'] or
434 "unpacked" in self.args['actions']):
435 self.run_command('strip -S "%(viewer_binary)s"' %
436 { 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
437
438
439 def package_finish(self):
440 channel_standin = 'Second Life' # hah, our default channel is not usable on its own
441 if not self.default_channel():
442 channel_standin = self.channel()
443
444 imagename="SecondLife_" + '_'.join(self.args['version'])
445
446 # MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
447 # If we really need differently named volumes, we'll need to create multiple DS_Store file images, or use some other trick.
448
449 volname="Second Life Installer" # DO NOT CHANGE without understanding comment above
450
451 if self.default_channel():
452 if not self.default_grid():
453 # beta case
454 imagename = imagename + '_' + self.args['grid'].upper()
455 else:
456 # first look, etc
457 imagename = imagename + '_' + self.channel_oneword().upper()
458
459 sparsename = imagename + ".sparseimage"
460 finalname = imagename + ".dmg"
461 # make sure we don't have stale files laying about
462 self.remove(sparsename, finalname)
463
464 self.run_command('hdiutil create "%(sparse)s" -volname "%(vol)s" -fs HFS+ -type SPARSE -megabytes 300 -layout SPUD' % {
465 'sparse':sparsename,
466 'vol':volname})
467
468 # mount the image and get the name of the mount point and device node
469 hdi_output = self.run_command('hdiutil attach -private "' + sparsename + '"')
470 devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
471 volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
472
473 # Copy everything in to the mounted .dmg
474
475 if self.default_channel() and not self.default_grid():
476 app_name = "Second Life " + self.args['grid']
477 else:
478 app_name = channel_standin.strip()
479
480 # Hack:
481 # Because there is no easy way to coerce the Finder into positioning
482 # the app bundle in the same place with different app names, we are
483 # adding multiple .DS_Store files to svn. There is one for release,
484 # one for release candidate and one for first look. Any other channels
485 # will use the release .DS_Store, and will look broken.
486 # - Ambroff 2008-08-20
487 dmg_template = os.path.join(
488 'installers',
489 'darwin',
490 '%s-dmg' % "".join(self.channel_unique().split()).lower())
491
492 if not os.path.exists (self.src_path_of(dmg_template)):
493 dmg_template = os.path.join ('installers', 'darwin', 'release-dmg')
494
495 # To reinstate the linden scripting guide, add this to the list below:
496 # "lsl_guide.html":"Linden Scripting Language Guide.html",
497
498 for s,d in {self.get_dst_prefix():app_name + ".app",
499 os.path.join(dmg_template, "_VolumeIcon.icns"): ".VolumeIcon.icns",
500 os.path.join(dmg_template, "background.jpg"): "background.jpg",
501 os.path.join(dmg_template, "_DS_Store"): ".DS_Store"}.items():
502 print "Copying to dmg", s, d
503 self.copy_action(self.src_path_of(s), os.path.join(volpath, d))
504
505 # Hide the background image, DS_Store file, and volume icon file (set their "visible" bit)
506 self.run_command('SetFile -a V "' + os.path.join(volpath, ".VolumeIcon.icns") + '"')
507 self.run_command('SetFile -a V "' + os.path.join(volpath, "background.jpg") + '"')
508 self.run_command('SetFile -a V "' + os.path.join(volpath, ".DS_Store") + '"')
509
510 # Create the alias file (which is a resource file) from the .r
511 self.run_command('rez "' + self.src_path_of("installers/darwin/release-dmg/Applications-alias.r") + '" -o "' + os.path.join(volpath, "Applications") + '"')
512
513 # Set the alias file's alias and custom icon bits
514 self.run_command('SetFile -a AC "' + os.path.join(volpath, "Applications") + '"')
515
516 # Set the disk image root's custom icon bit
517 self.run_command('SetFile -a C "' + volpath + '"')
518
519 # Unmount the image
520 self.run_command('hdiutil detach -force "' + devfile + '"')
521
522 print "Converting temp disk image to final disk image"
523 self.run_command('hdiutil convert "%(sparse)s" -format UDZO -imagekey zlib-level=9 -o "%(final)s"' % {'sparse':sparsename, 'final':finalname})
524 # get rid of the temp file
525 self.package_file = finalname
526 self.remove(sparsename)
441 527
442class LinuxManifest(ViewerManifest): 528class LinuxManifest(ViewerManifest):
443 def construct(self): 529 def construct(self):
444 super(LinuxManifest, self).construct() 530 super(LinuxManifest, self).construct()
445 self.path("licenses-linux.txt","licenses.txt") 531 self.path("licenses-linux.txt","licenses.txt")
446 self.path("res/ll_icon.png","secondlife_icon.png") 532 self.path("res/ll_icon.png","secondlife_icon.png")
447 if self.prefix("linux_tools", ""): 533 if self.prefix("linux_tools", dst=""):
448 self.path("client-readme.txt","README-linux.txt") 534 self.path("client-readme.txt","README-linux.txt")
449 self.path("client-readme-voice.txt","README-linux-voice.txt") 535 self.path("client-readme-voice.txt","README-linux-voice.txt")
450 self.path("wrapper.sh","secondlife") 536 self.path("wrapper.sh","secondlife")
451 self.path("handle_secondlifeprotocol.sh") 537 self.path("handle_secondlifeprotocol.sh")
452 self.path("register_secondlifeprotocol.sh") 538 self.path("register_secondlifeprotocol.sh")
453 self.end_prefix("linux_tools") 539 self.end_prefix("linux_tools")
454 540
455 # Create an appropriate gridargs.dat for this package, denoting required grid. 541 # Create an appropriate gridargs.dat for this package, denoting required grid.
456 self.put_in_file(self.flags_list(), 'gridargs.dat') 542 self.put_in_file(self.flags_list(), 'gridargs.dat')
457 543
458 544
459 def package_finish(self): 545 def package_finish(self):
460 # stripping all the libs removes a few megabytes from the end-user package 546 # stripping all the libs removes a few megabytes from the end-user package
461 for s,d in self.file_list: 547 for s,d in self.file_list:
462 if re.search("lib/lib.+\.so.*", d): 548 if re.search("lib/lib.+\.so.*", d):
463 self.run_command('strip -S %s' % d) 549 self.run_command('strip -S %s' % d)
464 if re.search("app_settings/mozilla-runtime-.*/lib.+\.so.*", d): 550 if re.search("app_settings/mozilla-runtime-.*/lib.+\.so.*", d):
465 self.run_command('strip %s' % d) 551 self.run_command('strip %s' % d)
466 552
467 if(self.args.has_key('installer_name')): 553 if 'installer_name' in self.args:
468 installer_name = self.args['installer_name'] 554 installer_name = self.args['installer_name']
469 else: 555 else:
470 installer_name = '_'.join('SecondLife_', self.args.get('arch'), *self.args['version']) 556 installer_name_components = ['SecondLife_', self.args.get('arch')]
471 if self.default_channel(): 557 installer_name_components.extend(self.args['version'])
472 if not self.default_grid(): 558 installer_name = "_".join(installer_name_components)
473 installer_name += '_' + self.args['grid'].upper() 559 if self.default_channel():
474 else: 560 if not self.default_grid():
475 installer_name += '_' + self.channel_oneword().upper() 561 installer_name += '_' + self.args['grid'].upper()
562 else:
563 installer_name += '_' + self.channel_oneword().upper()
476 564
477 # Fix access permissions 565 # Fix access permissions
478 self.run_command(""" 566 self.run_command("""
479 find %(dst)s -type d | xargs chmod 755; 567 find %(dst)s -type d | xargs --no-run-if-empty chmod 755;
480 find %(dst)s -type f -perm 0700 | xargs chmod 0755; 568 find %(dst)s -type f -perm 0700 | xargs --no-run-if-empty chmod 0755;
481 find %(dst)s -type f -perm 0500 | xargs chmod 0555; 569 find %(dst)s -type f -perm 0500 | xargs --no-run-if-empty chmod 0555;
482 find %(dst)s -type f -perm 0600 | xargs chmod 0644; 570 find %(dst)s -type f -perm 0600 | xargs --no-run-if-empty chmod 0644;
483 find %(dst)s -type f -perm 0400 | xargs chmod 0444; 571 find %(dst)s -type f -perm 0400 | xargs --no-run-if-empty chmod 0444;
484 true""" % {'dst':self.get_dst_prefix() }) 572 true""" % {'dst':self.get_dst_prefix() })
485 573
486 if("package" in self.args['actions'] or 574 self.package_file = installer_name + '.tar.bz2'
487 "unpacked" in self.args['actions']): 575
488 # temporarily move directory tree so that it has the right name in the tarfile 576 if("package" in self.args['actions'] or
489 self.run_command("mv %(dst)s %(inst)s" % {'dst':self.get_dst_prefix(),'inst':self.src_path_of(installer_name)}) 577 "unpacked" in self.args['actions']):
490 # --numeric-owner hides the username of the builder for security etc. 578
491 self.run_command('tar -C %(dir)s --numeric-owner -cjf %(inst_path)s.tar.bz2 %(inst_name)s' % {'dir':self.get_src_prefix(), 'inst_name': installer_name, 'inst_path':self.src_path_of(installer_name)}) 579 # temporarily move directory tree so that it has the right
492 self.run_command("mv %(inst)s %(dst)s" % {'dst':self.get_dst_prefix(),'inst':self.src_path_of(installer_name)}) 580 # name in the tarfile
581 self.run_command("mv %(dst)s %(inst)s" % {
582 'dst': self.get_dst_prefix(),
583 'inst': self.build_path_of(installer_name)})
584 try:
585 # --numeric-owner hides the username of the builder for
586 # security etc.
587 self.run_command('tar -C %(dir)s --numeric-owner -cjf '
588 '%(inst_path)s.tar.bz2 %(inst_name)s' % {
589 'dir': self.get_build_prefix(),
590 'inst_name': installer_name,
591 'inst_path':self.build_path_of(installer_name)})
592 finally:
593 self.run_command("mv %(inst)s %(dst)s" % {
594 'dst': self.get_dst_prefix(),
595 'inst': self.build_path_of(installer_name)})
493 596
494 597
495class Linux_i686Manifest(LinuxManifest): 598class Linux_i686Manifest(LinuxManifest):
496 def construct(self): 599 def construct(self):
497 super(Linux_i686Manifest, self).construct() 600 super(Linux_i686Manifest, self).construct()
498 self.path("secondlife-i686-bin-stripped","bin/do-not-directly-run-secondlife-bin") 601 self.path("secondlife-stripped","bin/do-not-directly-run-secondlife-bin")
499# self.path("../linux_crash_logger/linux-crash-logger-i686-bin-stripped","linux-crash-logger.bin") 602# self.path("../linux_crash_logger/linux-crash-logger-stripped","linux-crash-logger.bin")
500 self.path("linux_tools/launch_url.sh","launch_url.sh") 603 self.path("linux_tools/launch_url.sh","launch_url.sh")
501 if self.prefix("res-sdl"): 604 if self.prefix("res-sdl"):
502 self.path("*") 605 self.path("*")
503 # recurse 606 # recurse
504 self.end_prefix("res-sdl") 607 self.end_prefix("res-sdl")
505 608
506 self.path("featuretable_linux.txt") 609 self.path("featuretable_linux.txt")
507 #self.path("secondlife-i686.supp") 610 #self.path("secondlife-i686.supp")
508 611
509 self.path("app_settings/mozilla-runtime-linux-i686") 612 self.path("app_settings/mozilla-runtime-linux-i686")
510 613
511 if self.prefix("../../libraries/i686-linux/lib_release_client", "lib"): 614 if self.prefix("../../libraries/i686-linux/lib_release_client", dst="lib"):
512# self.path("libkdu_v42R.so") 615# self.path("libkdu_v42R.so")
513 self.path("libfmod-3.75.so") 616 self.path("libfmod-3.75.so")
514 self.path("libapr-1.so.0") 617 self.path("libapr-1.so.0")
515 self.path("libaprutil-1.so.0") 618 self.path("libaprutil-1.so.0")
516 self.path("libdb-4.2.so") 619 self.path("libdb-4.2.so")
517 self.path("libcrypto.so.0.9.7") 620 self.path("libcrypto.so.0.9.7")
518 self.path("libssl.so.0.9.7") 621 self.path("libssl.so.0.9.7")
519 self.path("libexpat.so.1") 622# self.path("libstdc++.so.6")
520# self.path("libstdc++.so.6") 623 self.path("libuuid.so", "libuuid.so.1")
521 self.path("libuuid.so", "libuuid.so.1") 624 self.path("libSDL-1.2.so.0")
522 self.path("libSDL-1.2.so.0") 625 self.path("libELFIO.so")
523 self.path("libELFIO.so") 626 self.path("libopenjpeg.so.2")
524 self.path("libopenjpeg.so.2") 627 #self.path("libtcmalloc.so.0") - bugged
525 #self.path("libtcmalloc.so.0") - bugged 628 #self.path("libstacktrace.so.0") - probably bugged
526 #self.path("libstacktrace.so.0") - probably bugged 629# self.path("libllkdu.so", "../bin/libllkdu.so") # llkdu goes in bin for some reason
527# self.path("libllkdu.so", "../bin/libllkdu.so") # llkdu goes in bin for some reason 630 self.end_prefix("lib")
528 self.end_prefix("lib") 631
529 632 # Vivox runtimes
530 # Vivox runtimes 633 if self.prefix(src="vivox-runtime/i686-linux", dst="bin"):
531 if self.prefix(src="vivox-runtime/i686-linux", dst=""): 634 self.path("SLVoice")
532 self.path("SLVoice") 635 self.end_prefix()
533 self.end_prefix() 636 if self.prefix(src="vivox-runtime/i686-linux", dst="lib"):
534 if self.prefix(src="vivox-runtime/i686-linux", dst="lib"): 637 self.path("libopenal.so.1")
535 self.path("libopenal.so.1") 638 self.path("libortp.so")
536 self.path("libortp.so") 639 self.path("libvivoxsdk.so")
537 self.path("libvivoxsdk.so") 640 self.path("libalut.so")
538 self.path("libalut.so") 641 self.end_prefix("lib")
539 self.end_prefix("lib")
540 642
541class Linux_x86_64Manifest(LinuxManifest): 643class Linux_x86_64Manifest(LinuxManifest):
542 def construct(self): 644 def construct(self):
543 super(Linux_x86_64Manifest, self).construct() 645 super(Linux_x86_64Manifest, self).construct()
544 self.path("secondlife-x86_64-bin-stripped","bin/do-not-directly-run-secondlife-bin") 646 self.path("secondlife-stripped","bin/do-not-directly-run-secondlife-bin")
545# self.path("../linux_crash_logger/linux-crash-logger-x86_64-bin-stripped","linux-crash-logger.bin") 647# self.path("../linux_crash_logger/linux-crash-logger-stripped","linux-crash-logger.bin")
546 self.path("linux_tools/launch_url.sh","launch_url.sh") 648 self.path("linux_tools/launch_url.sh","launch_url.sh")
547 if self.prefix("res-sdl"): 649 if self.prefix("res-sdl"):
548 self.path("*") 650 self.path("*")
549 # recurse 651 # recurse
550 self.end_prefix("res-sdl") 652 self.end_prefix("res-sdl")
551 653
552 self.path("featuretable_linux.txt") 654 self.path("featuretable_linux.txt")
553 self.path("secondlife-i686.supp") 655 self.path("secondlife-i686.supp")
554 656
555if __name__ == "__main__": 657if __name__ == "__main__":
556 main(srctree=viewer_dir, dsttree=os.path.join(viewer_dir, "packaged")) 658 main()